diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 5492f13a05..6b29adb4c4 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -14,6 +14,41 @@ from django.core.exceptions import ValidationError from django.utils.translation import ugettext as _ from .version import inventreeVersion, inventreeInstanceName +from .settings import MEDIA_URL, STATIC_URL + + +def getMediaUrl(filename): + """ + Return the qualified access path for the given file, + under the media directory. + """ + + return os.path.join(MEDIA_URL, str(filename)) + + +def getStaticUrl(filename): + """ + Return the qualified access path for the given file, + under the static media directory. + """ + + return os.path.join(STATIC_URL, str(filename)) + + +def getBlankImage(): + """ + Return the qualified path for the 'blank image' placeholder. + """ + + return getStaticUrl("img/blank_image.png") + + +def getBlankThumbnail(): + """ + Return the qualified path for the 'blank image' thumbnail placeholder. + """ + + return getStaticUrl("img/blank_image.thumbnail.png") def TestIfImage(img): @@ -66,7 +101,7 @@ def isNull(text): True if the text looks like a null value """ - return str(text).strip().lower() in ['top', 'null', 'none', 'empty', 'false', '-1'] + return str(text).strip().lower() in ['top', 'null', 'none', 'empty', 'false', '-1', ''] def decimal2string(d): diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index 2511cf4318..d93a40e631 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -1,3 +1,4 @@ + from django.test import TestCase import django.core.exceptions as django_exceptions from django.core.exceptions import ValidationError @@ -7,6 +8,8 @@ from . import helpers from mptt.exceptions import InvalidMove +from decimal import Decimal + from stock.models import StockLocation @@ -72,6 +75,29 @@ class TestHelpers(TestCase): self.assertFalse(helpers.str2bool(s)) self.assertFalse(helpers.str2bool(s, test=False)) + def test_isnull(self): + + for s in ['null', 'none', '', '-1', 'false']: + self.assertTrue(helpers.isNull(s)) + + for s in ['yes', 'frog', 'llama', 'true']: + self.assertFalse(helpers.isNull(s)) + + def testStaticUrl(self): + + self.assertEqual(helpers.getStaticUrl('test.jpg'), '/static/test.jpg') + self.assertEqual(helpers.getBlankImage(), '/static/img/blank_image.png') + self.assertEqual(helpers.getBlankThumbnail(), '/static/img/blank_image.thumbnail.png') + + def testMediaUrl(self): + + self.assertEqual(helpers.getMediaUrl('xx/yy.png'), '/media/xx/yy.png') + + def testDecimal2String(self): + + self.assertEqual(helpers.decimal2string(Decimal('1.2345000')), '1.2345') + self.assertEqual(helpers.decimal2string('test'), 'test') + class TestQuoteWrap(TestCase): """ Tests for string wrapping """ diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index ae812a2695..386409646a 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -5,7 +5,7 @@ Provides information on the current InvenTree version import subprocess from common.models import InvenTreeSetting -INVENTREE_SW_VERSION = "0.0.10" +INVENTREE_SW_VERSION = "0.0.11_pre" def inventreeInstanceName(): diff --git a/InvenTree/company/apps.py b/InvenTree/company/apps.py index c55451e882..949ee152c0 100644 --- a/InvenTree/company/apps.py +++ b/InvenTree/company/apps.py @@ -1,7 +1,36 @@ from __future__ import unicode_literals +import os + from django.apps import AppConfig +from django.db.utils import OperationalError, ProgrammingError +from django.conf import settings class CompanyConfig(AppConfig): name = 'company' + + def ready(self): + """ + This function is called whenever the Company app is loaded. + """ + + self.generate_company_thumbs() + + def generate_company_thumbs(self): + + from .models import Company + + print("InvenTree: Checking Company image thumbnails") + + try: + for company in Company.objects.all(): + if company.image: + url = company.image.thumbnail.name + loc = os.path.join(settings.MEDIA_ROOT, url) + + if not os.path.exists(loc): + print("InvenTree: Generating thumbnail for Company '{c}'".format(c=company.name)) + company.image.render_variations(replace=False) + except (OperationalError, ProgrammingError): + print("Could not generate Company thumbnails") diff --git a/InvenTree/company/migrations/0014_auto_20200407_0116.py b/InvenTree/company/migrations/0014_auto_20200407_0116.py new file mode 100644 index 0000000000..03985a1ef3 --- /dev/null +++ b/InvenTree/company/migrations/0014_auto_20200407_0116.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.10 on 2020-04-07 01:16 + +import company.models +from django.db import migrations +import stdimage.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0013_auto_20200406_0131'), + ] + + operations = [ + migrations.AlterField( + model_name='company', + name='image', + field=stdimage.models.StdImageField(blank=True, null=True, upload_to=company.models.rename_company_image), + ), + ] diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 6f14700184..462b4ff847 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -17,10 +17,12 @@ from django.db.models import Sum from django.apps import apps from django.urls import reverse -from django.conf import settings from markdownx.models import MarkdownxField +from stdimage.models import StdImageField + +from InvenTree.helpers import getMediaUrl, getBlankImage, getBlankThumbnail from InvenTree.fields import InvenTreeURLField, RoundingDecimalField from InvenTree.status_codes import OrderStatus from common.models import Currency @@ -90,7 +92,13 @@ class Company(models.Model): link = InvenTreeURLField(blank=True, help_text=_('Link to external company information')) - image = models.ImageField(upload_to=rename_company_image, max_length=255, null=True, blank=True) + image = StdImageField( + upload_to=rename_company_image, + null=True, + blank=True, + variations={'thumbnail': (128, 128)}, + delete_orphans=True, + ) notes = MarkdownxField(blank=True) @@ -110,10 +118,18 @@ class Company(models.Model): """ Return the URL of the image for this company """ if self.image: - return os.path.join(settings.MEDIA_URL, str(self.image.url)) + return getMediaUrl(self.image.url) else: - return os.path.join(settings.STATIC_URL, 'img/blank_image.png') + return getBlankImage() + def get_thumbnail_url(self): + """ Return the URL for the thumbnail image for this Company """ + + if self.image: + return getMediaUrl(self.image.thumbnail.url) + else: + return getBlankThumbnail() + @property def part_count(self): """ The number of parts supplied by this company """ diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py index 161edd286e..935712a180 100644 --- a/InvenTree/company/serializers.py +++ b/InvenTree/company/serializers.py @@ -32,7 +32,7 @@ class CompanySerializer(InvenTreeModelSerializer): url = serializers.CharField(source='get_absolute_url', read_only=True) part_count = serializers.CharField(read_only=True) - image = serializers.CharField(source='get_image_url', read_only=True) + image = serializers.CharField(source='get_thumbnail_url', read_only=True) class Meta: model = Company @@ -64,7 +64,7 @@ class SupplierPartSerializer(InvenTreeModelSerializer): part_detail = PartBriefSerializer(source='part', many=False, read_only=True) supplier_name = serializers.CharField(source='supplier.name', read_only=True) - supplier_logo = serializers.CharField(source='supplier.get_image_url', read_only=True) + supplier_logo = serializers.CharField(source='supplier.get_thumbnail_url', read_only=True) pricing = serializers.CharField(source='unit_pricing', read_only=True) diff --git a/InvenTree/company/templates/company/company_base.html b/InvenTree/company/templates/company/company_base.html index 19ef2a0e60..8ec68a401c 100644 --- a/InvenTree/company/templates/company/company_base.html +++ b/InvenTree/company/templates/company/company_base.html @@ -46,7 +46,7 @@ InvenTree | {% trans "Company" %} - {{ company.name }} {% if company.website %} - + {% trans "Website" %} {{ company.website }} diff --git a/InvenTree/locale/de/LC_MESSAGES/django.mo b/InvenTree/locale/de/LC_MESSAGES/django.mo index 1632eb47b2..4038658f79 100644 Binary files a/InvenTree/locale/de/LC_MESSAGES/django.mo and b/InvenTree/locale/de/LC_MESSAGES/django.mo differ diff --git a/InvenTree/locale/de/LC_MESSAGES/django.po b/InvenTree/locale/de/LC_MESSAGES/django.po index 22ea2c52ad..212ae38002 100644 --- a/InvenTree/locale/de/LC_MESSAGES/django.po +++ b/InvenTree/locale/de/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-05 10:55+0000\n" +"POT-Creation-Date: 2020-04-09 15:04+0000\n" "PO-Revision-Date: 2020-02-02 08:07+0100\n" "Last-Translator: Christian Schlüter \n" "Language-Team: C \n" @@ -17,30 +17,30 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Lokalize 19.12.0\n" -#: InvenTree/helpers.py:201 order/models.py:164 order/models.py:215 +#: InvenTree/helpers.py:240 order/models.py:164 order/models.py:215 msgid "Invalid quantity provided" msgstr "Keine gültige Menge" -#: InvenTree/helpers.py:204 +#: InvenTree/helpers.py:243 msgid "Empty serial number string" msgstr "Keine Seriennummer angegeben" -#: InvenTree/helpers.py:225 InvenTree/helpers.py:242 +#: InvenTree/helpers.py:264 InvenTree/helpers.py:281 #, python-brace-format msgid "Duplicate serial: {n}" msgstr "Doppelte Seriennummer: {n}" -#: InvenTree/helpers.py:229 InvenTree/helpers.py:232 InvenTree/helpers.py:235 -#: InvenTree/helpers.py:246 +#: InvenTree/helpers.py:268 InvenTree/helpers.py:271 InvenTree/helpers.py:274 +#: InvenTree/helpers.py:285 #, python-brace-format msgid "Invalid group: {g}" msgstr "Ungültige Gruppe: {g}" -#: InvenTree/helpers.py:252 +#: InvenTree/helpers.py:291 msgid "No serial numbers found" msgstr "Keine Seriennummern gefunden" -#: InvenTree/helpers.py:256 +#: InvenTree/helpers.py:295 #, python-brace-format msgid "Number of unique serial number ({s}) must match quantity ({q})" msgstr "" @@ -113,7 +113,7 @@ msgstr "Zerstört" #: InvenTree/status_codes.py:98 build/templates/build/allocate_edit.html:28 #: build/templates/build/allocate_view.html:21 -#: part/templates/part/part_base.html:106 part/templates/part/tabs.html:21 +#: part/templates/part/part_base.html:109 part/templates/part/tabs.html:21 msgid "Allocated" msgstr "Zugeordnet" @@ -142,7 +142,7 @@ msgstr "Überschuss darf 100% nicht überschreiten" msgid "Overage must be an integer value or a percentage" msgstr "Überschuss muss eine Ganzzahl oder ein Prozentwert sein" -#: InvenTree/views.py:548 +#: InvenTree/views.py:549 msgid "Database Statistics" msgstr "" @@ -186,7 +186,7 @@ msgstr "Bau-Status" msgid "Batch code for this build output" msgstr "Chargennummer für diese Bau-Ausgabe" -#: build/models.py:97 +#: build/models.py:97 stock/models.py:331 msgid "Link to external URL" msgstr "Link zu einer externen URL" @@ -231,7 +231,7 @@ msgstr "Zuweisung aufheben" #: build/templates/build/allocate_edit.html:19 #: build/templates/build/allocate_view.html:17 -#: build/templates/build/detail.html:17 +#: build/templates/build/detail.html:21 #: company/templates/company/detail_part.html:65 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:25 @@ -267,12 +267,12 @@ msgstr "Teile bestellen" #: company/templates/company/supplier_part_base.html:50 #: company/templates/company/supplier_part_detail.html:27 #: order/templates/order/purchase_order_detail.html:26 -#: part/templates/part/detail.html:37 +#: part/templates/part/detail.html:38 msgid "Description" msgstr "Beschreibung" #: build/templates/build/allocate_view.html:22 -#: part/templates/part/part_base.html:112 +#: part/templates/part/part_base.html:115 msgid "On Order" msgstr "bestellt" @@ -293,65 +293,64 @@ msgstr "" msgid "Build Details" msgstr "Bau-Status" -#: build/templates/build/detail.html:14 +#: build/templates/build/detail.html:16 msgid "Title" msgstr "Titel" -#: build/templates/build/detail.html:20 +#: build/templates/build/detail.html:26 #: company/templates/company/supplier_part_pricing.html:27 #: order/templates/order/order_wizard/select_parts.html:32 #: order/templates/order/purchase_order_detail.html:29 -#: stock/templates/stock/item_base.html:101 +#: stock/templates/stock/item_base.html:107 #: stock/templates/stock/stock_adjust.html:18 msgid "Quantity" msgstr "Anzahl" -#: build/templates/build/detail.html:23 +#: build/templates/build/detail.html:30 msgid "Stock Source" msgstr "Lagerobjekt" -#: build/templates/build/detail.html:28 +#: build/templates/build/detail.html:35 msgid "Stock can be taken from any available location." msgstr "Bestand kann jedem verfügbaren Lagerort entnommen werden." -#: build/templates/build/detail.html:33 +#: build/templates/build/detail.html:41 #: order/templates/order/order_base.html:71 -#: stock/templates/stock/item_base.html:158 +#: stock/templates/stock/item_base.html:174 msgid "Status" msgstr "Status" -#: build/templates/build/detail.html:37 -#: stock/templates/stock/item_base.html:107 +#: build/templates/build/detail.html:47 +#: stock/templates/stock/item_base.html:114 msgid "Batch" msgstr "Los" -#: build/templates/build/detail.html:42 -#: company/templates/company/detail_part.html:90 +#: build/templates/build/detail.html:54 #: company/templates/company/supplier_part_base.html:47 #: company/templates/company/supplier_part_detail.html:24 -#: part/templates/part/part_base.html:81 -#: stock/templates/stock/item_base.html:131 -msgid "URL" -msgstr "URL" +#: part/templates/part/detail.html:67 part/templates/part/part_base.html:84 +#: stock/templates/stock/item_base.html:142 +msgid "External Link" +msgstr "" -#: build/templates/build/detail.html:46 -#: order/templates/order/order_base.html:75 +#: build/templates/build/detail.html:60 +#: order/templates/order/order_base.html:83 msgid "Created" msgstr "Erstellt" -#: build/templates/build/detail.html:50 +#: build/templates/build/detail.html:66 msgid "Enough Parts?" msgstr "Genügend Teile?" -#: build/templates/build/detail.html:53 +#: build/templates/build/detail.html:69 msgid "Yes" msgstr "Ja" -#: build/templates/build/detail.html:55 +#: build/templates/build/detail.html:71 msgid "No" msgstr "Nein" -#: build/templates/build/detail.html:62 +#: build/templates/build/detail.html:79 msgid "Completed" msgstr "Fertig" @@ -380,7 +379,7 @@ msgstr "Details" msgid "Outputs" msgstr "" -#: build/templates/build/tabs.html:11 company/models.py:248 +#: build/templates/build/tabs.html:11 company/models.py:264 #: company/templates/company/tabs.html:26 order/templates/order/tabs.html:15 #: part/templates/part/tabs.html:58 stock/templates/stock/tabs.html:17 msgid "Notes" @@ -571,81 +570,81 @@ msgstr "" msgid "Delete Currency" msgstr "" -#: company/models.py:74 +#: company/models.py:76 msgid "Company name" msgstr "Firmenname" -#: company/models.py:76 +#: company/models.py:78 msgid "Description of the company" msgstr "Firmenbeschreibung" -#: company/models.py:78 +#: company/models.py:80 msgid "Company website URL" msgstr "Firmenwebsite" -#: company/models.py:81 +#: company/models.py:83 msgid "Company address" msgstr "Firmenadresse" -#: company/models.py:84 +#: company/models.py:86 msgid "Contact phone number" msgstr "Kontakt-Tel." -#: company/models.py:86 +#: company/models.py:88 msgid "Contact email address" msgstr "Kontakt-Email" -#: company/models.py:89 +#: company/models.py:91 msgid "Point of contact" msgstr "Anlaufstelle" -#: company/models.py:91 +#: company/models.py:93 msgid "Link to external company information" msgstr "Link auf externe Firmeninformation" -#: company/models.py:97 +#: company/models.py:105 msgid "Do you sell items to this company?" msgstr "Verkaufen Sie Teile an diese Firma?" -#: company/models.py:99 +#: company/models.py:107 msgid "Do you purchase items from this company?" msgstr "Kaufen Sie Teile von dieser Firma?" -#: company/models.py:229 +#: company/models.py:245 msgid "Select part" msgstr "Teil auswählen" -#: company/models.py:235 +#: company/models.py:251 msgid "Select supplier" msgstr "Zulieferer auswählen" -#: company/models.py:238 +#: company/models.py:254 msgid "Supplier stock keeping unit" msgstr "Stock Keeping Units (SKU) des Zulieferers" -#: company/models.py:240 company/templates/company/detail_part.html:81 +#: company/models.py:256 company/templates/company/detail_part.html:81 #: company/templates/company/supplier_part_base.html:53 #: company/templates/company/supplier_part_detail.html:30 msgid "Manufacturer" msgstr "Hersteller" -#: company/models.py:242 +#: company/models.py:258 msgid "Manufacturer part number" msgstr "Hersteller-Teilenummer" -#: company/models.py:244 +#: company/models.py:260 msgid "URL for external supplier part link" msgstr "Teil-URL des Zulieferers" -#: company/models.py:246 +#: company/models.py:262 msgid "Supplier part description" msgstr "Zuliefererbeschreibung des Teils" -#: company/models.py:250 +#: company/models.py:266 msgid "Minimum charge (e.g. stocking fee)" msgstr "Mindestpreis" -#: company/models.py:252 +#: company/models.py:268 msgid "Part packaging" msgstr "Teile-Packaging" @@ -653,24 +652,24 @@ msgstr "Teile-Packaging" msgid "Company" msgstr "Firma" -#: company/templates/company/company_base.html:48 +#: company/templates/company/company_base.html:50 #: company/templates/company/index.html:59 msgid "Website" msgstr "" -#: company/templates/company/company_base.html:53 +#: company/templates/company/company_base.html:57 msgid "Address" msgstr "" -#: company/templates/company/company_base.html:58 +#: company/templates/company/company_base.html:64 msgid "Phone" msgstr "" -#: company/templates/company/company_base.html:63 +#: company/templates/company/company_base.html:71 msgid "Email" msgstr "" -#: company/templates/company/company_base.html:68 +#: company/templates/company/company_base.html:78 msgid "Contact" msgstr "" @@ -680,18 +679,18 @@ msgstr "" msgid "Company Details" msgstr "Firmenbemerkungen" -#: company/templates/company/detail.html:13 -#: stock/templates/stock/item_base.html:125 +#: company/templates/company/detail.html:16 +#: stock/templates/stock/item_base.html:135 msgid "Customer" msgstr "Kunde" -#: company/templates/company/detail.html:17 +#: company/templates/company/detail.html:21 #: company/templates/company/index.html:46 #: company/templates/company/supplier_part_base.html:44 #: company/templates/company/supplier_part_detail.html:21 -#: order/templates/order/order_base.html:67 +#: order/templates/order/order_base.html:66 #: order/templates/order/order_wizard/select_pos.html:30 -#: stock/templates/stock/item_base.html:137 +#: stock/templates/stock/item_base.html:149 msgid "Supplier" msgstr "Zulieferer" @@ -723,6 +722,10 @@ msgstr "Anhang löschen" msgid "SKU" msgstr "" +#: company/templates/company/detail_part.html:90 +msgid "Link" +msgstr "" + #: company/templates/company/detail_purchase_orders.html:8 #: company/templates/company/tabs.html:15 part/templates/part/tabs.html:43 msgid "Purchase Orders" @@ -763,8 +766,8 @@ msgstr "Zulieferer" msgid "ID" msgstr "" -#: company/templates/company/index.html:69 part/templates/part/category.html:81 -#: templates/navbar.html:10 templates/stats.html:7 templates/stats.html:10 +#: company/templates/company/index.html:69 part/templates/part/category.html:83 +#: templates/navbar.html:10 templates/stats.html:8 templates/stats.html:17 msgid "Parts" msgstr "Teile" @@ -780,7 +783,7 @@ msgstr "" #: company/templates/company/supplier_part_base.html:6 #: company/templates/company/supplier_part_base.html:13 -#: stock/templates/stock/item_base.html:141 +#: stock/templates/stock/item_base.html:154 msgid "Supplier Part" msgstr "Zulieferer-Teil" @@ -1003,7 +1006,7 @@ msgid "Order notes" msgstr "Bestell-Notizen" #: order/models.py:162 order/models.py:213 part/views.py:1119 -#: stock/models.py:463 +#: stock/models.py:467 msgid "Quantity must be greater than zero" msgstr "Anzahl muss größer Null sein" @@ -1027,7 +1030,7 @@ msgstr "Position - Referenz" msgid "Line item notes" msgstr "Position - Notizen" -#: order/models.py:298 stock/templates/stock/item_base.html:119 +#: order/models.py:298 stock/templates/stock/item_base.html:128 msgid "Purchase Order" msgstr "Kaufvertrag" @@ -1039,15 +1042,15 @@ msgstr "Zulieferer-Teil" msgid "Number of items received" msgstr "Empfangene Objekt-Anzahl" -#: order/templates/order/order_base.html:64 +#: order/templates/order/order_base.html:61 msgid "Purchase Order Details" msgstr "Bestelldetails" -#: order/templates/order/order_base.html:80 +#: order/templates/order/order_base.html:89 msgid "Issued" msgstr "Aufgegeben" -#: order/templates/order/order_base.html:86 +#: order/templates/order/order_base.html:96 #: order/templates/order/purchase_order_detail.html:31 msgid "Received" msgstr "Empfangen" @@ -1362,173 +1365,173 @@ msgstr "Eintragsmenge zur Preisberechnung" msgid "Select currency for price calculation" msgstr "Währung zur Preisberechnung wählen" -#: part/models.py:62 +#: part/models.py:61 msgid "Default location for parts in this category" msgstr "Standard-Standort für Teile dieser Kategorie" -#: part/models.py:65 +#: part/models.py:64 msgid "Default keywords for parts in this category" msgstr "Standard-Stichworte für Teile dieser Kategorie" -#: part/models.py:339 +#: part/models.py:338 msgid "Part must be unique for name, IPN and revision" msgstr "Namen, Teile- und Revisionsnummern müssen eindeutig sein" -#: part/models.py:353 +#: part/models.py:352 msgid "Part cannot be a template part if it is a variant of another part" msgstr "Teil kann keine Vorlage sein wenn es Variante eines anderen Teils ist" -#: part/models.py:354 +#: part/models.py:353 msgid "Part cannot be a variant of another part if it is already a template" msgstr "" "Teil kann keine Variante eines anderen Teils sein wenn es bereits eine " "Vorlage ist" -#: part/models.py:358 part/templates/part/detail.html:18 +#: part/models.py:357 part/templates/part/detail.html:19 msgid "Part name" msgstr "Name des Teils" -#: part/models.py:362 +#: part/models.py:361 msgid "Is this part a template part?" msgstr "Ist dieses Teil eine Vorlage?" -#: part/models.py:371 +#: part/models.py:370 msgid "Is this part a variant of another part?" msgstr "Ist dieses Teil eine Variante eines anderen Teils?" -#: part/models.py:373 +#: part/models.py:372 msgid "Part description" msgstr "Beschreibung des Teils" -#: part/models.py:375 +#: part/models.py:374 msgid "Part keywords to improve visibility in search results" msgstr "Schlüsselworte um die Sichtbarkeit in Suchergebnissen zu verbessern" -#: part/models.py:380 +#: part/models.py:379 msgid "Part category" msgstr "Teile-Kategorie" -#: part/models.py:382 +#: part/models.py:381 msgid "Internal Part Number" msgstr "Interne Teilenummer" -#: part/models.py:384 +#: part/models.py:383 msgid "Part revision or version number" msgstr "Revisions- oder Versionsnummer" -#: part/models.py:386 +#: part/models.py:385 msgid "Link to extenal URL" msgstr "Link zu einer Externen URL" -#: part/models.py:398 +#: part/models.py:397 msgid "Where is this item normally stored?" msgstr "Wo wird dieses Teil normalerweise gelagert?" -#: part/models.py:442 +#: part/models.py:441 msgid "Default supplier part" msgstr "Standard-Zulieferer?" -#: part/models.py:445 +#: part/models.py:444 msgid "Minimum allowed stock level" msgstr "Minimal zulässiger Lagerbestand" -#: part/models.py:447 +#: part/models.py:446 msgid "Stock keeping units for this part" msgstr "Stock Keeping Units (SKU) für dieses Teil" -#: part/models.py:449 +#: part/models.py:448 msgid "Can this part be built from other parts?" msgstr "Kann dieses Teil aus anderen Teilen angefertigt werden?" -#: part/models.py:451 +#: part/models.py:450 msgid "Can this part be used to build other parts?" msgstr "Kann dieses Teil zum Bau von anderen genutzt werden?" -#: part/models.py:453 +#: part/models.py:452 msgid "Does this part have tracking for unique items?" msgstr "Hat dieses Teil Tracking für einzelne Objekte?" -#: part/models.py:455 +#: part/models.py:454 msgid "Can this part be purchased from external suppliers?" msgstr "Kann dieses Teil von externen Zulieferern gekauft werden?" -#: part/models.py:457 +#: part/models.py:456 msgid "Can this part be sold to customers?" msgstr "Kann dieses Teil an Kunden verkauft werden?" -#: part/models.py:459 +#: part/models.py:458 msgid "Is this part active?" msgstr "Ist dieses Teil aktiv?" -#: part/models.py:461 +#: part/models.py:460 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:463 +#: part/models.py:462 msgid "Part notes - supports Markdown formatting" msgstr "Bemerkungen - unterstüzt Markdown-Formatierung" -#: part/models.py:465 +#: part/models.py:464 msgid "Stored BOM checksum" msgstr "Prüfsumme der Stückliste gespeichert" -#: part/models.py:1041 +#: part/models.py:1040 msgid "Parameter template name must be unique" msgstr "Vorlagen-Name des Parameters muss eindeutig sein" -#: part/models.py:1046 +#: part/models.py:1045 msgid "Parameter Name" msgstr "Name des Parameters" -#: part/models.py:1048 +#: part/models.py:1047 msgid "Parameter Units" msgstr "Parameter Einheit" -#: part/models.py:1074 +#: part/models.py:1073 msgid "Parent Part" msgstr "Ausgangsteil" -#: part/models.py:1076 +#: part/models.py:1075 msgid "Parameter Template" msgstr "Parameter Vorlage" -#: part/models.py:1078 +#: part/models.py:1077 msgid "Parameter Value" msgstr "Parameter Wert" -#: part/models.py:1102 +#: part/models.py:1101 msgid "Select parent part" msgstr "Ausgangsteil auswählen" -#: part/models.py:1111 +#: part/models.py:1110 msgid "Select part to be used in BOM" msgstr "Teil für die Nutzung in der Stückliste auswählen" -#: part/models.py:1118 +#: part/models.py:1117 msgid "BOM quantity for this BOM item" msgstr "Stücklisten-Anzahl für dieses Stücklisten-Teil" -#: part/models.py:1121 +#: part/models.py:1120 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "Geschätzter Ausschuss (absolut oder prozentual)" -#: part/models.py:1124 +#: part/models.py:1123 msgid "BOM item reference" msgstr "Referenz des Objekts auf der Stückliste" -#: part/models.py:1127 +#: part/models.py:1126 msgid "BOM item notes" msgstr "Notizen zum Stücklisten-Objekt" -#: part/models.py:1129 +#: part/models.py:1128 msgid "BOM line checksum" msgstr "Prüfsumme der Stückliste" -#: part/models.py:1192 +#: part/models.py:1191 msgid "Part cannot be added to its own Bill of Materials" msgstr "Teil kann nicht zu seiner eigenen Stückliste hinzugefügt werden" -#: part/models.py:1199 +#: part/models.py:1198 #, 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)" @@ -1537,8 +1540,8 @@ msgstr "Teil '{p1}' wird in Stückliste für Teil '{p2}' benutzt (rekursiv)" msgid "Part Attachments" msgstr "Anhänge" -#: part/templates/part/category.html:13 part/templates/part/category.html:76 -#: templates/stats.html:14 +#: part/templates/part/category.html:13 part/templates/part/category.html:78 +#: templates/stats.html:12 msgid "Part Categories" msgstr "Teile-Kategorien" @@ -1546,31 +1549,31 @@ msgstr "Teile-Kategorien" msgid "All parts" msgstr "Alle Teile" -#: part/templates/part/category.html:34 part/templates/part/category.html:72 +#: part/templates/part/category.html:34 part/templates/part/category.html:73 msgid "Category Details" msgstr "Kategorie-Details" -#: part/templates/part/category.html:38 +#: part/templates/part/category.html:39 msgid "Category Path" msgstr "Pfad zur Kategorie" -#: part/templates/part/category.html:43 +#: part/templates/part/category.html:44 msgid "Category Description" msgstr "Kategorie-Beschreibung" -#: part/templates/part/category.html:49 part/templates/part/detail.html:73 +#: part/templates/part/category.html:50 part/templates/part/detail.html:74 msgid "Default Location" msgstr "Standard-Lagerort" -#: part/templates/part/category.html:56 part/templates/part/detail.html:50 +#: part/templates/part/category.html:57 part/templates/part/detail.html:51 msgid "Keywords" msgstr "Schlüsselwörter" -#: part/templates/part/category.html:62 +#: part/templates/part/category.html:63 msgid "Subcategories" msgstr "Unter-Kategorien" -#: part/templates/part/category.html:67 +#: part/templates/part/category.html:68 msgid "Parts (Including subcategories)" msgstr "Teile (inklusive Unter-Kategorien)" @@ -1578,119 +1581,115 @@ msgstr "Teile (inklusive Unter-Kategorien)" msgid "Part Details" msgstr "Teile-Details" -#: part/templates/part/detail.html:24 part/templates/part/part_base.html:75 +#: part/templates/part/detail.html:25 part/templates/part/part_base.html:77 msgid "IPN" msgstr "IPN (Interne Produktnummer)" -#: part/templates/part/detail.html:31 +#: part/templates/part/detail.html:32 msgid "Revision" msgstr "Revision" -#: part/templates/part/detail.html:43 +#: part/templates/part/detail.html:44 msgid "Variant Of" msgstr "Variante von" -#: part/templates/part/detail.html:56 +#: part/templates/part/detail.html:57 msgid "Category" msgstr "Kategorie" -#: part/templates/part/detail.html:66 -msgid "Link" -msgstr "" - -#: part/templates/part/detail.html:80 +#: part/templates/part/detail.html:81 msgid "Default Supplier" msgstr "Standard-Zulieferer" -#: part/templates/part/detail.html:88 +#: part/templates/part/detail.html:89 msgid "Units" msgstr "Einheiten" -#: part/templates/part/detail.html:94 +#: part/templates/part/detail.html:95 msgid "Minimum Stock" msgstr "Minimaler Lagerbestand" -#: part/templates/part/detail.html:100 +#: part/templates/part/detail.html:101 #, fuzzy #| msgid "Create new Stock Item" msgid "Creation Date" msgstr "Neues Lagerobjekt hinzufügen" -#: part/templates/part/detail.html:106 +#: part/templates/part/detail.html:107 #, fuzzy #| msgid "Created" msgid "Created By" msgstr "Erstellt" -#: part/templates/part/detail.html:113 +#: part/templates/part/detail.html:114 msgid "Responsible User" msgstr "" -#: part/templates/part/detail.html:122 +#: part/templates/part/detail.html:123 msgid "Virtual" msgstr "Virtuell" -#: part/templates/part/detail.html:125 +#: part/templates/part/detail.html:126 msgid "Part is virtual (not a physical part)" msgstr "Teil ist virtuell (kein physisches Teil)" -#: part/templates/part/detail.html:127 +#: part/templates/part/detail.html:128 msgid "Part is not a virtual part" msgstr "Teil ist nicht virtuell" -#: part/templates/part/detail.html:131 +#: part/templates/part/detail.html:132 msgid "Assembly" msgstr "Baugruppe" -#: part/templates/part/detail.html:134 +#: part/templates/part/detail.html:135 msgid "Part can be assembled from other parts" msgstr "Teil kann aus anderen Teilen angefertigt werden" -#: part/templates/part/detail.html:136 +#: part/templates/part/detail.html:137 msgid "Part cannot be assembled from other parts" msgstr "Teil kann nicht aus anderen Teilen angefertigt werden" -#: part/templates/part/detail.html:140 +#: part/templates/part/detail.html:141 msgid "Component" msgstr "Komponente" -#: part/templates/part/detail.html:143 +#: part/templates/part/detail.html:144 msgid "Part can be used in assemblies" msgstr "Teil kann in Baugruppen benutzt werden" -#: part/templates/part/detail.html:145 +#: part/templates/part/detail.html:146 msgid "Part cannot be used in assemblies" msgstr "Teil kann nicht in Baugruppen benutzt werden" -#: part/templates/part/detail.html:149 +#: part/templates/part/detail.html:150 msgid "Trackable" msgstr "nachverfolgbar" -#: part/templates/part/detail.html:152 +#: part/templates/part/detail.html:153 msgid "Part stock is tracked by serial number" msgstr "Teilebestand in der Seriennummer hinterlegt" -#: part/templates/part/detail.html:154 +#: part/templates/part/detail.html:155 msgid "Part stock is not tracked by serial number" msgstr "Teilebestand ist nicht in der Seriennummer hinterlegt" -#: part/templates/part/detail.html:158 +#: part/templates/part/detail.html:159 msgid "Purchaseable" msgstr "Kaufbar" -#: part/templates/part/detail.html:161 part/templates/part/detail.html:163 +#: part/templates/part/detail.html:162 part/templates/part/detail.html:164 msgid "Part can be purchased from external suppliers" msgstr "Teil kann von externen Zulieferern gekauft werden" -#: part/templates/part/detail.html:168 +#: part/templates/part/detail.html:169 msgid "Sellable" msgstr "Verkaufbar" -#: part/templates/part/detail.html:171 +#: part/templates/part/detail.html:172 msgid "Part can be sold to customers" msgstr "Teil kann an Kunden verkauft werden" -#: part/templates/part/detail.html:173 +#: part/templates/part/detail.html:174 msgid "Part cannot be sold to customers" msgstr "Teil kann nicht an Kunden verkauft werden" @@ -1722,23 +1721,23 @@ msgstr "Teil favorisieren" msgid "Show pricing information" msgstr "Kosteninformationen ansehen" -#: part/templates/part/part_base.html:95 +#: part/templates/part/part_base.html:98 msgid "Available Stock" msgstr "Verfügbarer Lagerbestand" -#: part/templates/part/part_base.html:100 +#: part/templates/part/part_base.html:103 msgid "In Stock" msgstr "Auf Lager" -#: part/templates/part/part_base.html:121 +#: part/templates/part/part_base.html:124 msgid "Build Status" msgstr "Bau-Status" -#: part/templates/part/part_base.html:125 +#: part/templates/part/part_base.html:128 msgid "Can Build" msgstr "Herstellbar?" -#: part/templates/part/part_base.html:130 +#: part/templates/part/part_base.html:133 msgid "Underway" msgstr "unterwegs" @@ -1788,7 +1787,7 @@ msgstr "Varianten" msgid "BOM" msgstr "Stückliste" -#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:113 +#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:121 #: templates/navbar.html:12 msgid "Build" msgstr "Bau" @@ -1932,93 +1931,93 @@ msgstr "Anzahl angeben" msgid "Export Bill of Materials" msgstr "" -#: part/views.py:1406 +#: part/views.py:1404 #, fuzzy #| msgid "Confirm part creation" msgid "Confirm Part Deletion" msgstr "Erstellen des Teils bestätigen" -#: part/views.py:1413 +#: part/views.py:1411 msgid "Part was deleted" msgstr "" -#: part/views.py:1422 +#: part/views.py:1420 #, fuzzy #| msgid "Part packaging" msgid "Part Pricing" msgstr "Teile-Packaging" -#: part/views.py:1544 +#: part/views.py:1542 #, fuzzy #| msgid "Parameter Template" msgid "Create Part Parameter Template" msgstr "Parameter Vorlage" -#: part/views.py:1552 +#: part/views.py:1550 #, fuzzy #| msgid "Parameter Template" msgid "Edit Part Parameter Template" msgstr "Parameter Vorlage" -#: part/views.py:1559 +#: part/views.py:1557 #, fuzzy #| msgid "Parameter Template" msgid "Delete Part Parameter Template" msgstr "Parameter Vorlage" -#: part/views.py:1567 +#: part/views.py:1565 msgid "Create Part Parameter" msgstr "" -#: part/views.py:1617 +#: part/views.py:1615 #, fuzzy #| msgid "Edit attachment" msgid "Edit Part Parameter" msgstr "Anhang bearbeiten" -#: part/views.py:1631 +#: part/views.py:1629 #, fuzzy #| msgid "Delete attachment" msgid "Delete Part Parameter" msgstr "Anhang löschen" -#: part/views.py:1647 +#: part/views.py:1645 #, fuzzy #| msgid "Part category" msgid "Edit Part Category" msgstr "Teile-Kategorie" -#: part/views.py:1682 +#: part/views.py:1680 #, fuzzy #| msgid "Select part category" msgid "Delete Part Category" msgstr "Teilekategorie wählen" -#: part/views.py:1688 +#: part/views.py:1686 #, fuzzy #| msgid "Part category" msgid "Part category was deleted" msgstr "Teile-Kategorie" -#: part/views.py:1696 +#: part/views.py:1694 #, fuzzy #| msgid "Select part category" msgid "Create new part category" msgstr "Teilekategorie wählen" -#: part/views.py:1747 +#: part/views.py:1745 #, fuzzy #| msgid "Created new stock item" msgid "Create BOM item" msgstr "Neues Lagerobjekt erstellt" -#: part/views.py:1813 +#: part/views.py:1811 #, fuzzy #| msgid "Edit Stock Item" msgid "Edit BOM item" msgstr "Lagerobjekt bearbeiten" -#: part/views.py:1861 +#: part/views.py:1859 #, fuzzy #| msgid "Confirm build completion" msgid "Confim BOM item deletion" @@ -2119,45 +2118,45 @@ msgstr "Objekt löschen wenn Lagerbestand aufgebraucht" msgid "Stock Item Notes" msgstr "Lagerobjekt-Notizen" -#: stock/models.py:460 +#: stock/models.py:464 msgid "Quantity must be integer" msgstr "Anzahl muss eine Ganzzahl sein" -#: stock/models.py:466 +#: stock/models.py:470 #, 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:469 stock/models.py:472 +#: stock/models.py:473 stock/models.py:476 msgid "Serial numbers must be a list of integers" msgstr "Seriennummern muss eine Liste von Ganzzahlen sein" -#: stock/models.py:475 +#: stock/models.py:479 msgid "Quantity does not match serial numbers" msgstr "Anzahl stimmt nicht mit den Seriennummern überein" -#: stock/models.py:485 +#: stock/models.py:489 msgid "Serial numbers already exist: " msgstr "Seriennummern existieren bereits:" -#: stock/models.py:507 +#: stock/models.py:511 msgid "Add serial number" msgstr "Seriennummer hinzufügen" -#: stock/models.py:510 +#: stock/models.py:514 #, python-brace-format msgid "Serialized {n} items" msgstr "{n} Teile serialisiert" -#: stock/models.py:810 +#: stock/models.py:814 msgid "Tracking entry title" msgstr "Name des Eintrags-Trackings" -#: stock/models.py:812 +#: stock/models.py:816 msgid "Entry notes" msgstr "Eintrags-Notizen" -#: stock/models.py:814 +#: stock/models.py:818 msgid "Link to external page for further information" msgstr "Link auf externe Seite für weitere Informationen" @@ -2194,28 +2193,28 @@ msgstr "" msgid "This stock item was split from " msgstr "" -#: stock/templates/stock/item_base.html:85 +#: stock/templates/stock/item_base.html:88 msgid "Belongs To" msgstr "Gehört zu" -#: stock/templates/stock/item_base.html:90 +#: stock/templates/stock/item_base.html:94 #: stock/templates/stock/stock_adjust.html:17 msgid "Location" msgstr "Standort" -#: stock/templates/stock/item_base.html:96 +#: stock/templates/stock/item_base.html:101 msgid "Serial Number" msgstr "Seriennummer" -#: stock/templates/stock/item_base.html:146 +#: stock/templates/stock/item_base.html:160 msgid "Last Updated" msgstr "Zuletzt aktualisiert" -#: stock/templates/stock/item_base.html:150 +#: stock/templates/stock/item_base.html:165 msgid "Last Stocktake" msgstr "Letzte Inventur" -#: stock/templates/stock/item_base.html:154 +#: stock/templates/stock/item_base.html:169 msgid "No stocktake performed" msgstr "Keine Inventur ausgeführt" @@ -2233,29 +2232,29 @@ msgstr "" msgid "Location Details" msgstr "Standort-Details" -#: stock/templates/stock/location.html:41 +#: stock/templates/stock/location.html:42 msgid "Location Path" msgstr "Standord-Pfad" -#: stock/templates/stock/location.html:46 +#: stock/templates/stock/location.html:47 msgid "Location Description" msgstr "Standort-Beschreibung" -#: stock/templates/stock/location.html:51 +#: stock/templates/stock/location.html:52 msgid "Sublocations" msgstr "Sub-Standorte" -#: stock/templates/stock/location.html:56 -#: stock/templates/stock/location.html:70 templates/stats.html:18 -#: templates/stats.html:21 +#: stock/templates/stock/location.html:57 +#: stock/templates/stock/location.html:72 templates/stats.html:21 +#: templates/stats.html:30 msgid "Stock Items" msgstr "Lagerobjekte" -#: stock/templates/stock/location.html:61 +#: stock/templates/stock/location.html:62 msgid "Stock Details" msgstr "Objekt-Details" -#: stock/templates/stock/location.html:65 +#: stock/templates/stock/location.html:67 #: templates/InvenTree/search_stock_location.html:6 templates/stats.html:25 msgid "Stock Locations" msgstr "Lagerobjekt-Standorte" @@ -2433,30 +2432,40 @@ msgstr "" msgid "No results found" msgstr "Keine Seriennummern gefunden" -#: templates/about.html:18 +#: templates/about.html:13 msgid "InvenTree Version Information" msgstr "InvenTree-Versionsinformationen" #: templates/about.html:21 -msgid "Version" -msgstr "Version" +msgid "Instance Name" +msgstr "" -#: templates/about.html:24 +#: templates/about.html:26 +#, fuzzy +#| msgid "InvenTree Version Information" +msgid "InvenTree Version" +msgstr "InvenTree-Versionsinformationen" + +#: templates/about.html:30 msgid "Commit Hash" msgstr "Commit-Hash" -#: templates/about.html:27 +#: templates/about.html:34 msgid "Commit Date" msgstr "Commit-Datum" -#: templates/about.html:33 +#: templates/about.html:38 msgid "InvenTree Documentation" msgstr "InvenTree-Dokumentation" -#: templates/about.html:37 +#: templates/about.html:43 msgid "View Code on GitHub" msgstr "Code auf GitHub ansehen" +#: templates/about.html:47 +msgid "Submit Bug Report" +msgstr "" + #: templates/navbar.html:23 msgid "Admin" msgstr "" @@ -2530,3 +2539,9 @@ msgstr "bestellt" #| msgid "Delete Stock Item" msgid "Delete Stock" msgstr "Lagerobjekt löschen" + +#~ msgid "URL" +#~ msgstr "URL" + +#~ msgid "Version" +#~ msgstr "Version" diff --git a/InvenTree/locale/en/LC_MESSAGES/django.po b/InvenTree/locale/en/LC_MESSAGES/django.po index def566c9da..9291ccd461 100644 --- a/InvenTree/locale/en/LC_MESSAGES/django.po +++ b/InvenTree/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-05 10:55+0000\n" +"POT-Creation-Date: 2020-04-09 15:04+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,30 +18,30 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: InvenTree/helpers.py:201 order/models.py:164 order/models.py:215 +#: InvenTree/helpers.py:240 order/models.py:164 order/models.py:215 msgid "Invalid quantity provided" msgstr "" -#: InvenTree/helpers.py:204 +#: InvenTree/helpers.py:243 msgid "Empty serial number string" msgstr "" -#: InvenTree/helpers.py:225 InvenTree/helpers.py:242 +#: InvenTree/helpers.py:264 InvenTree/helpers.py:281 #, python-brace-format msgid "Duplicate serial: {n}" msgstr "" -#: InvenTree/helpers.py:229 InvenTree/helpers.py:232 InvenTree/helpers.py:235 -#: InvenTree/helpers.py:246 +#: InvenTree/helpers.py:268 InvenTree/helpers.py:271 InvenTree/helpers.py:274 +#: InvenTree/helpers.py:285 #, python-brace-format msgid "Invalid group: {g}" msgstr "" -#: InvenTree/helpers.py:252 +#: InvenTree/helpers.py:291 msgid "No serial numbers found" msgstr "" -#: InvenTree/helpers.py:256 +#: InvenTree/helpers.py:295 #, python-brace-format msgid "Number of unique serial number ({s}) must match quantity ({q})" msgstr "" @@ -112,7 +112,7 @@ msgstr "" #: InvenTree/status_codes.py:98 build/templates/build/allocate_edit.html:28 #: build/templates/build/allocate_view.html:21 -#: part/templates/part/part_base.html:106 part/templates/part/tabs.html:21 +#: part/templates/part/part_base.html:109 part/templates/part/tabs.html:21 msgid "Allocated" msgstr "" @@ -141,7 +141,7 @@ msgstr "" msgid "Overage must be an integer value or a percentage" msgstr "" -#: InvenTree/views.py:548 +#: InvenTree/views.py:549 msgid "Database Statistics" msgstr "" @@ -183,7 +183,7 @@ msgstr "" msgid "Batch code for this build output" msgstr "" -#: build/models.py:97 +#: build/models.py:97 stock/models.py:331 msgid "Link to external URL" msgstr "" @@ -227,7 +227,7 @@ msgstr "" #: build/templates/build/allocate_edit.html:19 #: build/templates/build/allocate_view.html:17 -#: build/templates/build/detail.html:17 +#: build/templates/build/detail.html:21 #: company/templates/company/detail_part.html:65 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:25 @@ -263,12 +263,12 @@ msgstr "" #: company/templates/company/supplier_part_base.html:50 #: company/templates/company/supplier_part_detail.html:27 #: order/templates/order/purchase_order_detail.html:26 -#: part/templates/part/detail.html:37 +#: part/templates/part/detail.html:38 msgid "Description" msgstr "" #: build/templates/build/allocate_view.html:22 -#: part/templates/part/part_base.html:112 +#: part/templates/part/part_base.html:115 msgid "On Order" msgstr "" @@ -284,65 +284,64 @@ msgstr "" msgid "Build Details" msgstr "" -#: build/templates/build/detail.html:14 +#: build/templates/build/detail.html:16 msgid "Title" msgstr "" -#: build/templates/build/detail.html:20 +#: build/templates/build/detail.html:26 #: company/templates/company/supplier_part_pricing.html:27 #: order/templates/order/order_wizard/select_parts.html:32 #: order/templates/order/purchase_order_detail.html:29 -#: stock/templates/stock/item_base.html:101 +#: stock/templates/stock/item_base.html:107 #: stock/templates/stock/stock_adjust.html:18 msgid "Quantity" msgstr "" -#: build/templates/build/detail.html:23 +#: build/templates/build/detail.html:30 msgid "Stock Source" msgstr "" -#: build/templates/build/detail.html:28 +#: build/templates/build/detail.html:35 msgid "Stock can be taken from any available location." msgstr "" -#: build/templates/build/detail.html:33 +#: build/templates/build/detail.html:41 #: order/templates/order/order_base.html:71 -#: stock/templates/stock/item_base.html:158 +#: stock/templates/stock/item_base.html:174 msgid "Status" msgstr "" -#: build/templates/build/detail.html:37 -#: stock/templates/stock/item_base.html:107 +#: build/templates/build/detail.html:47 +#: stock/templates/stock/item_base.html:114 msgid "Batch" msgstr "" -#: build/templates/build/detail.html:42 -#: company/templates/company/detail_part.html:90 +#: build/templates/build/detail.html:54 #: company/templates/company/supplier_part_base.html:47 #: company/templates/company/supplier_part_detail.html:24 -#: part/templates/part/part_base.html:81 -#: stock/templates/stock/item_base.html:131 -msgid "URL" +#: part/templates/part/detail.html:67 part/templates/part/part_base.html:84 +#: stock/templates/stock/item_base.html:142 +msgid "External Link" msgstr "" -#: build/templates/build/detail.html:46 -#: order/templates/order/order_base.html:75 +#: build/templates/build/detail.html:60 +#: order/templates/order/order_base.html:83 msgid "Created" msgstr "" -#: build/templates/build/detail.html:50 +#: build/templates/build/detail.html:66 msgid "Enough Parts?" msgstr "" -#: build/templates/build/detail.html:53 +#: build/templates/build/detail.html:69 msgid "Yes" msgstr "" -#: build/templates/build/detail.html:55 +#: build/templates/build/detail.html:71 msgid "No" msgstr "" -#: build/templates/build/detail.html:62 +#: build/templates/build/detail.html:79 msgid "Completed" msgstr "" @@ -371,7 +370,7 @@ msgstr "" msgid "Outputs" msgstr "" -#: build/templates/build/tabs.html:11 company/models.py:248 +#: build/templates/build/tabs.html:11 company/models.py:264 #: company/templates/company/tabs.html:26 order/templates/order/tabs.html:15 #: part/templates/part/tabs.html:58 stock/templates/stock/tabs.html:17 msgid "Notes" @@ -530,81 +529,81 @@ msgstr "" msgid "Delete Currency" msgstr "" -#: company/models.py:74 +#: company/models.py:76 msgid "Company name" msgstr "" -#: company/models.py:76 +#: company/models.py:78 msgid "Description of the company" msgstr "" -#: company/models.py:78 +#: company/models.py:80 msgid "Company website URL" msgstr "" -#: company/models.py:81 +#: company/models.py:83 msgid "Company address" msgstr "" -#: company/models.py:84 +#: company/models.py:86 msgid "Contact phone number" msgstr "" -#: company/models.py:86 +#: company/models.py:88 msgid "Contact email address" msgstr "" -#: company/models.py:89 +#: company/models.py:91 msgid "Point of contact" msgstr "" -#: company/models.py:91 +#: company/models.py:93 msgid "Link to external company information" msgstr "" -#: company/models.py:97 +#: company/models.py:105 msgid "Do you sell items to this company?" msgstr "" -#: company/models.py:99 +#: company/models.py:107 msgid "Do you purchase items from this company?" msgstr "" -#: company/models.py:229 +#: company/models.py:245 msgid "Select part" msgstr "" -#: company/models.py:235 +#: company/models.py:251 msgid "Select supplier" msgstr "" -#: company/models.py:238 +#: company/models.py:254 msgid "Supplier stock keeping unit" msgstr "" -#: company/models.py:240 company/templates/company/detail_part.html:81 +#: company/models.py:256 company/templates/company/detail_part.html:81 #: company/templates/company/supplier_part_base.html:53 #: company/templates/company/supplier_part_detail.html:30 msgid "Manufacturer" msgstr "" -#: company/models.py:242 +#: company/models.py:258 msgid "Manufacturer part number" msgstr "" -#: company/models.py:244 +#: company/models.py:260 msgid "URL for external supplier part link" msgstr "" -#: company/models.py:246 +#: company/models.py:262 msgid "Supplier part description" msgstr "" -#: company/models.py:250 +#: company/models.py:266 msgid "Minimum charge (e.g. stocking fee)" msgstr "" -#: company/models.py:252 +#: company/models.py:268 msgid "Part packaging" msgstr "" @@ -612,24 +611,24 @@ msgstr "" msgid "Company" msgstr "" -#: company/templates/company/company_base.html:48 +#: company/templates/company/company_base.html:50 #: company/templates/company/index.html:59 msgid "Website" msgstr "" -#: company/templates/company/company_base.html:53 +#: company/templates/company/company_base.html:57 msgid "Address" msgstr "" -#: company/templates/company/company_base.html:58 +#: company/templates/company/company_base.html:64 msgid "Phone" msgstr "" -#: company/templates/company/company_base.html:63 +#: company/templates/company/company_base.html:71 msgid "Email" msgstr "" -#: company/templates/company/company_base.html:68 +#: company/templates/company/company_base.html:78 msgid "Contact" msgstr "" @@ -637,18 +636,18 @@ msgstr "" msgid "Company Details" msgstr "" -#: company/templates/company/detail.html:13 -#: stock/templates/stock/item_base.html:125 +#: company/templates/company/detail.html:16 +#: stock/templates/stock/item_base.html:135 msgid "Customer" msgstr "" -#: company/templates/company/detail.html:17 +#: company/templates/company/detail.html:21 #: company/templates/company/index.html:46 #: company/templates/company/supplier_part_base.html:44 #: company/templates/company/supplier_part_detail.html:21 -#: order/templates/order/order_base.html:67 +#: order/templates/order/order_base.html:66 #: order/templates/order/order_wizard/select_pos.html:30 -#: stock/templates/stock/item_base.html:137 +#: stock/templates/stock/item_base.html:149 msgid "Supplier" msgstr "" @@ -676,6 +675,10 @@ msgstr "" msgid "SKU" msgstr "" +#: company/templates/company/detail_part.html:90 +msgid "Link" +msgstr "" + #: company/templates/company/detail_purchase_orders.html:8 #: company/templates/company/tabs.html:15 part/templates/part/tabs.html:43 msgid "Purchase Orders" @@ -708,8 +711,8 @@ msgstr "" msgid "ID" msgstr "" -#: company/templates/company/index.html:69 part/templates/part/category.html:81 -#: templates/navbar.html:10 templates/stats.html:7 templates/stats.html:10 +#: company/templates/company/index.html:69 part/templates/part/category.html:83 +#: templates/navbar.html:10 templates/stats.html:8 templates/stats.html:17 msgid "Parts" msgstr "" @@ -724,7 +727,7 @@ msgstr "" #: company/templates/company/supplier_part_base.html:6 #: company/templates/company/supplier_part_base.html:13 -#: stock/templates/stock/item_base.html:141 +#: stock/templates/stock/item_base.html:154 msgid "Supplier Part" msgstr "" @@ -903,7 +906,7 @@ msgid "Order notes" msgstr "" #: order/models.py:162 order/models.py:213 part/views.py:1119 -#: stock/models.py:463 +#: stock/models.py:467 msgid "Quantity must be greater than zero" msgstr "" @@ -927,7 +930,7 @@ msgstr "" msgid "Line item notes" msgstr "" -#: order/models.py:298 stock/templates/stock/item_base.html:119 +#: order/models.py:298 stock/templates/stock/item_base.html:128 msgid "Purchase Order" msgstr "" @@ -939,15 +942,15 @@ msgstr "" msgid "Number of items received" msgstr "" -#: order/templates/order/order_base.html:64 +#: order/templates/order/order_base.html:61 msgid "Purchase Order Details" msgstr "" -#: order/templates/order/order_base.html:80 +#: order/templates/order/order_base.html:89 msgid "Issued" msgstr "" -#: order/templates/order/order_base.html:86 +#: order/templates/order/order_base.html:96 #: order/templates/order/purchase_order_detail.html:31 msgid "Received" msgstr "" @@ -1213,171 +1216,171 @@ msgstr "" msgid "Select currency for price calculation" msgstr "" -#: part/models.py:62 +#: part/models.py:61 msgid "Default location for parts in this category" msgstr "" -#: part/models.py:65 +#: part/models.py:64 msgid "Default keywords for parts in this category" msgstr "" -#: part/models.py:339 +#: part/models.py:338 msgid "Part must be unique for name, IPN and revision" msgstr "" -#: part/models.py:353 +#: part/models.py:352 msgid "Part cannot be a template part if it is a variant of another part" msgstr "" -#: part/models.py:354 +#: part/models.py:353 msgid "Part cannot be a variant of another part if it is already a template" msgstr "" -#: part/models.py:358 part/templates/part/detail.html:18 +#: part/models.py:357 part/templates/part/detail.html:19 msgid "Part name" msgstr "" -#: part/models.py:362 +#: part/models.py:361 msgid "Is this part a template part?" msgstr "" -#: part/models.py:371 +#: part/models.py:370 msgid "Is this part a variant of another part?" msgstr "" -#: part/models.py:373 +#: part/models.py:372 msgid "Part description" msgstr "" -#: part/models.py:375 +#: part/models.py:374 msgid "Part keywords to improve visibility in search results" msgstr "" -#: part/models.py:380 +#: part/models.py:379 msgid "Part category" msgstr "" -#: part/models.py:382 +#: part/models.py:381 msgid "Internal Part Number" msgstr "" -#: part/models.py:384 +#: part/models.py:383 msgid "Part revision or version number" msgstr "" -#: part/models.py:386 +#: part/models.py:385 msgid "Link to extenal URL" msgstr "" -#: part/models.py:398 +#: part/models.py:397 msgid "Where is this item normally stored?" msgstr "" -#: part/models.py:442 +#: part/models.py:441 msgid "Default supplier part" msgstr "" -#: part/models.py:445 +#: part/models.py:444 msgid "Minimum allowed stock level" msgstr "" -#: part/models.py:447 +#: part/models.py:446 msgid "Stock keeping units for this part" msgstr "" -#: part/models.py:449 +#: part/models.py:448 msgid "Can this part be built from other parts?" msgstr "" -#: part/models.py:451 +#: part/models.py:450 msgid "Can this part be used to build other parts?" msgstr "" -#: part/models.py:453 +#: part/models.py:452 msgid "Does this part have tracking for unique items?" msgstr "" -#: part/models.py:455 +#: part/models.py:454 msgid "Can this part be purchased from external suppliers?" msgstr "" -#: part/models.py:457 +#: part/models.py:456 msgid "Can this part be sold to customers?" msgstr "" -#: part/models.py:459 +#: part/models.py:458 msgid "Is this part active?" msgstr "" -#: part/models.py:461 +#: part/models.py:460 msgid "Is this a virtual part, such as a software product or license?" msgstr "" -#: part/models.py:463 +#: part/models.py:462 msgid "Part notes - supports Markdown formatting" msgstr "" -#: part/models.py:465 +#: part/models.py:464 msgid "Stored BOM checksum" msgstr "" -#: part/models.py:1041 +#: part/models.py:1040 msgid "Parameter template name must be unique" msgstr "" -#: part/models.py:1046 +#: part/models.py:1045 msgid "Parameter Name" msgstr "" -#: part/models.py:1048 +#: part/models.py:1047 msgid "Parameter Units" msgstr "" -#: part/models.py:1074 +#: part/models.py:1073 msgid "Parent Part" msgstr "" -#: part/models.py:1076 +#: part/models.py:1075 msgid "Parameter Template" msgstr "" -#: part/models.py:1078 +#: part/models.py:1077 msgid "Parameter Value" msgstr "" -#: part/models.py:1102 +#: part/models.py:1101 msgid "Select parent part" msgstr "" -#: part/models.py:1111 +#: part/models.py:1110 msgid "Select part to be used in BOM" msgstr "" -#: part/models.py:1118 +#: part/models.py:1117 msgid "BOM quantity for this BOM item" msgstr "" -#: part/models.py:1121 +#: part/models.py:1120 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "" -#: part/models.py:1124 +#: part/models.py:1123 msgid "BOM item reference" msgstr "" -#: part/models.py:1127 +#: part/models.py:1126 msgid "BOM item notes" msgstr "" -#: part/models.py:1129 +#: part/models.py:1128 msgid "BOM line checksum" msgstr "" -#: part/models.py:1192 +#: part/models.py:1191 msgid "Part cannot be added to its own Bill of Materials" msgstr "" -#: part/models.py:1199 +#: part/models.py:1198 #, python-brace-format msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" msgstr "" @@ -1386,8 +1389,8 @@ msgstr "" msgid "Part Attachments" msgstr "" -#: part/templates/part/category.html:13 part/templates/part/category.html:76 -#: templates/stats.html:14 +#: part/templates/part/category.html:13 part/templates/part/category.html:78 +#: templates/stats.html:12 msgid "Part Categories" msgstr "" @@ -1395,31 +1398,31 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:34 part/templates/part/category.html:72 +#: part/templates/part/category.html:34 part/templates/part/category.html:73 msgid "Category Details" msgstr "" -#: part/templates/part/category.html:38 +#: part/templates/part/category.html:39 msgid "Category Path" msgstr "" -#: part/templates/part/category.html:43 +#: part/templates/part/category.html:44 msgid "Category Description" msgstr "" -#: part/templates/part/category.html:49 part/templates/part/detail.html:73 +#: part/templates/part/category.html:50 part/templates/part/detail.html:74 msgid "Default Location" msgstr "" -#: part/templates/part/category.html:56 part/templates/part/detail.html:50 +#: part/templates/part/category.html:57 part/templates/part/detail.html:51 msgid "Keywords" msgstr "" -#: part/templates/part/category.html:62 +#: part/templates/part/category.html:63 msgid "Subcategories" msgstr "" -#: part/templates/part/category.html:67 +#: part/templates/part/category.html:68 msgid "Parts (Including subcategories)" msgstr "" @@ -1427,115 +1430,111 @@ msgstr "" msgid "Part Details" msgstr "" -#: part/templates/part/detail.html:24 part/templates/part/part_base.html:75 +#: part/templates/part/detail.html:25 part/templates/part/part_base.html:77 msgid "IPN" msgstr "" -#: part/templates/part/detail.html:31 +#: part/templates/part/detail.html:32 msgid "Revision" msgstr "" -#: part/templates/part/detail.html:43 +#: part/templates/part/detail.html:44 msgid "Variant Of" msgstr "" -#: part/templates/part/detail.html:56 +#: part/templates/part/detail.html:57 msgid "Category" msgstr "" -#: part/templates/part/detail.html:66 -msgid "Link" -msgstr "" - -#: part/templates/part/detail.html:80 +#: part/templates/part/detail.html:81 msgid "Default Supplier" msgstr "" -#: part/templates/part/detail.html:88 +#: part/templates/part/detail.html:89 msgid "Units" msgstr "" -#: part/templates/part/detail.html:94 +#: part/templates/part/detail.html:95 msgid "Minimum Stock" msgstr "" -#: part/templates/part/detail.html:100 +#: part/templates/part/detail.html:101 msgid "Creation Date" msgstr "" -#: part/templates/part/detail.html:106 +#: part/templates/part/detail.html:107 msgid "Created By" msgstr "" -#: part/templates/part/detail.html:113 +#: part/templates/part/detail.html:114 msgid "Responsible User" msgstr "" -#: part/templates/part/detail.html:122 +#: part/templates/part/detail.html:123 msgid "Virtual" msgstr "" -#: part/templates/part/detail.html:125 +#: part/templates/part/detail.html:126 msgid "Part is virtual (not a physical part)" msgstr "" -#: part/templates/part/detail.html:127 +#: part/templates/part/detail.html:128 msgid "Part is not a virtual part" msgstr "" -#: part/templates/part/detail.html:131 +#: part/templates/part/detail.html:132 msgid "Assembly" msgstr "" -#: part/templates/part/detail.html:134 +#: part/templates/part/detail.html:135 msgid "Part can be assembled from other parts" msgstr "" -#: part/templates/part/detail.html:136 +#: part/templates/part/detail.html:137 msgid "Part cannot be assembled from other parts" msgstr "" -#: part/templates/part/detail.html:140 +#: part/templates/part/detail.html:141 msgid "Component" msgstr "" -#: part/templates/part/detail.html:143 +#: part/templates/part/detail.html:144 msgid "Part can be used in assemblies" msgstr "" -#: part/templates/part/detail.html:145 +#: part/templates/part/detail.html:146 msgid "Part cannot be used in assemblies" msgstr "" -#: part/templates/part/detail.html:149 +#: part/templates/part/detail.html:150 msgid "Trackable" msgstr "" -#: part/templates/part/detail.html:152 +#: part/templates/part/detail.html:153 msgid "Part stock is tracked by serial number" msgstr "" -#: part/templates/part/detail.html:154 +#: part/templates/part/detail.html:155 msgid "Part stock is not tracked by serial number" msgstr "" -#: part/templates/part/detail.html:158 +#: part/templates/part/detail.html:159 msgid "Purchaseable" msgstr "" -#: part/templates/part/detail.html:161 part/templates/part/detail.html:163 +#: part/templates/part/detail.html:162 part/templates/part/detail.html:164 msgid "Part can be purchased from external suppliers" msgstr "" -#: part/templates/part/detail.html:168 +#: part/templates/part/detail.html:169 msgid "Sellable" msgstr "" -#: part/templates/part/detail.html:171 +#: part/templates/part/detail.html:172 msgid "Part can be sold to customers" msgstr "" -#: part/templates/part/detail.html:173 +#: part/templates/part/detail.html:174 msgid "Part cannot be sold to customers" msgstr "" @@ -1563,23 +1562,23 @@ msgstr "" msgid "Show pricing information" msgstr "" -#: part/templates/part/part_base.html:95 +#: part/templates/part/part_base.html:98 msgid "Available Stock" msgstr "" -#: part/templates/part/part_base.html:100 +#: part/templates/part/part_base.html:103 msgid "In Stock" msgstr "" -#: part/templates/part/part_base.html:121 +#: part/templates/part/part_base.html:124 msgid "Build Status" msgstr "" -#: part/templates/part/part_base.html:125 +#: part/templates/part/part_base.html:128 msgid "Can Build" msgstr "" -#: part/templates/part/part_base.html:130 +#: part/templates/part/part_base.html:133 msgid "Underway" msgstr "" @@ -1619,7 +1618,7 @@ msgstr "" msgid "BOM" msgstr "" -#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:113 +#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:121 #: templates/navbar.html:12 msgid "Build" msgstr "" @@ -1737,67 +1736,67 @@ msgstr "" msgid "Export Bill of Materials" msgstr "" -#: part/views.py:1406 +#: part/views.py:1404 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1413 +#: part/views.py:1411 msgid "Part was deleted" msgstr "" -#: part/views.py:1422 +#: part/views.py:1420 msgid "Part Pricing" msgstr "" -#: part/views.py:1544 +#: part/views.py:1542 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1552 +#: part/views.py:1550 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1559 +#: part/views.py:1557 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1567 +#: part/views.py:1565 msgid "Create Part Parameter" msgstr "" -#: part/views.py:1617 +#: part/views.py:1615 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:1631 +#: part/views.py:1629 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:1647 +#: part/views.py:1645 msgid "Edit Part Category" msgstr "" -#: part/views.py:1682 +#: part/views.py:1680 msgid "Delete Part Category" msgstr "" -#: part/views.py:1688 +#: part/views.py:1686 msgid "Part category was deleted" msgstr "" -#: part/views.py:1696 +#: part/views.py:1694 msgid "Create new part category" msgstr "" -#: part/views.py:1747 +#: part/views.py:1745 msgid "Create BOM item" msgstr "" -#: part/views.py:1813 +#: part/views.py:1811 msgid "Edit BOM item" msgstr "" -#: part/views.py:1861 +#: part/views.py:1859 msgid "Confim BOM item deletion" msgstr "" @@ -1893,45 +1892,45 @@ msgstr "" msgid "Stock Item Notes" msgstr "" -#: stock/models.py:460 +#: stock/models.py:464 msgid "Quantity must be integer" msgstr "" -#: stock/models.py:466 +#: stock/models.py:470 #, python-brace-format msgid "Quantity must not exceed available stock quantity ({n})" msgstr "" -#: stock/models.py:469 stock/models.py:472 +#: stock/models.py:473 stock/models.py:476 msgid "Serial numbers must be a list of integers" msgstr "" -#: stock/models.py:475 +#: stock/models.py:479 msgid "Quantity does not match serial numbers" msgstr "" -#: stock/models.py:485 +#: stock/models.py:489 msgid "Serial numbers already exist: " msgstr "" -#: stock/models.py:507 +#: stock/models.py:511 msgid "Add serial number" msgstr "" -#: stock/models.py:510 +#: stock/models.py:514 #, python-brace-format msgid "Serialized {n} items" msgstr "" -#: stock/models.py:810 +#: stock/models.py:814 msgid "Tracking entry title" msgstr "" -#: stock/models.py:812 +#: stock/models.py:816 msgid "Entry notes" msgstr "" -#: stock/models.py:814 +#: stock/models.py:818 msgid "Link to external page for further information" msgstr "" @@ -1962,28 +1961,28 @@ msgstr "" msgid "This stock item was split from " msgstr "" -#: stock/templates/stock/item_base.html:85 +#: stock/templates/stock/item_base.html:88 msgid "Belongs To" msgstr "" -#: stock/templates/stock/item_base.html:90 +#: stock/templates/stock/item_base.html:94 #: stock/templates/stock/stock_adjust.html:17 msgid "Location" msgstr "" -#: stock/templates/stock/item_base.html:96 +#: stock/templates/stock/item_base.html:101 msgid "Serial Number" msgstr "" -#: stock/templates/stock/item_base.html:146 +#: stock/templates/stock/item_base.html:160 msgid "Last Updated" msgstr "" -#: stock/templates/stock/item_base.html:150 +#: stock/templates/stock/item_base.html:165 msgid "Last Stocktake" msgstr "" -#: stock/templates/stock/item_base.html:154 +#: stock/templates/stock/item_base.html:169 msgid "No stocktake performed" msgstr "" @@ -1999,29 +1998,29 @@ msgstr "" msgid "Location Details" msgstr "" -#: stock/templates/stock/location.html:41 +#: stock/templates/stock/location.html:42 msgid "Location Path" msgstr "" -#: stock/templates/stock/location.html:46 +#: stock/templates/stock/location.html:47 msgid "Location Description" msgstr "" -#: stock/templates/stock/location.html:51 +#: stock/templates/stock/location.html:52 msgid "Sublocations" msgstr "" -#: stock/templates/stock/location.html:56 -#: stock/templates/stock/location.html:70 templates/stats.html:18 -#: templates/stats.html:21 +#: stock/templates/stock/location.html:57 +#: stock/templates/stock/location.html:72 templates/stats.html:21 +#: templates/stats.html:30 msgid "Stock Items" msgstr "" -#: stock/templates/stock/location.html:61 +#: stock/templates/stock/location.html:62 msgid "Stock Details" msgstr "" -#: stock/templates/stock/location.html:65 +#: stock/templates/stock/location.html:67 #: templates/InvenTree/search_stock_location.html:6 templates/stats.html:25 msgid "Stock Locations" msgstr "" @@ -2194,30 +2193,38 @@ msgstr "" msgid "No results found" msgstr "" -#: templates/about.html:18 +#: templates/about.html:13 msgid "InvenTree Version Information" msgstr "" #: templates/about.html:21 -msgid "Version" +msgid "Instance Name" msgstr "" -#: templates/about.html:24 +#: templates/about.html:26 +msgid "InvenTree Version" +msgstr "" + +#: templates/about.html:30 msgid "Commit Hash" msgstr "" -#: templates/about.html:27 +#: templates/about.html:34 msgid "Commit Date" msgstr "" -#: templates/about.html:33 +#: templates/about.html:38 msgid "InvenTree Documentation" msgstr "" -#: templates/about.html:37 +#: templates/about.html:43 msgid "View Code on GitHub" msgstr "" +#: templates/about.html:47 +msgid "Submit Bug Report" +msgstr "" + #: templates/navbar.html:23 msgid "Admin" msgstr "" diff --git a/InvenTree/locale/es/LC_MESSAGES/django.po b/InvenTree/locale/es/LC_MESSAGES/django.po index def566c9da..9291ccd461 100644 --- a/InvenTree/locale/es/LC_MESSAGES/django.po +++ b/InvenTree/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-05 10:55+0000\n" +"POT-Creation-Date: 2020-04-09 15:04+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,30 +18,30 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: InvenTree/helpers.py:201 order/models.py:164 order/models.py:215 +#: InvenTree/helpers.py:240 order/models.py:164 order/models.py:215 msgid "Invalid quantity provided" msgstr "" -#: InvenTree/helpers.py:204 +#: InvenTree/helpers.py:243 msgid "Empty serial number string" msgstr "" -#: InvenTree/helpers.py:225 InvenTree/helpers.py:242 +#: InvenTree/helpers.py:264 InvenTree/helpers.py:281 #, python-brace-format msgid "Duplicate serial: {n}" msgstr "" -#: InvenTree/helpers.py:229 InvenTree/helpers.py:232 InvenTree/helpers.py:235 -#: InvenTree/helpers.py:246 +#: InvenTree/helpers.py:268 InvenTree/helpers.py:271 InvenTree/helpers.py:274 +#: InvenTree/helpers.py:285 #, python-brace-format msgid "Invalid group: {g}" msgstr "" -#: InvenTree/helpers.py:252 +#: InvenTree/helpers.py:291 msgid "No serial numbers found" msgstr "" -#: InvenTree/helpers.py:256 +#: InvenTree/helpers.py:295 #, python-brace-format msgid "Number of unique serial number ({s}) must match quantity ({q})" msgstr "" @@ -112,7 +112,7 @@ msgstr "" #: InvenTree/status_codes.py:98 build/templates/build/allocate_edit.html:28 #: build/templates/build/allocate_view.html:21 -#: part/templates/part/part_base.html:106 part/templates/part/tabs.html:21 +#: part/templates/part/part_base.html:109 part/templates/part/tabs.html:21 msgid "Allocated" msgstr "" @@ -141,7 +141,7 @@ msgstr "" msgid "Overage must be an integer value or a percentage" msgstr "" -#: InvenTree/views.py:548 +#: InvenTree/views.py:549 msgid "Database Statistics" msgstr "" @@ -183,7 +183,7 @@ msgstr "" msgid "Batch code for this build output" msgstr "" -#: build/models.py:97 +#: build/models.py:97 stock/models.py:331 msgid "Link to external URL" msgstr "" @@ -227,7 +227,7 @@ msgstr "" #: build/templates/build/allocate_edit.html:19 #: build/templates/build/allocate_view.html:17 -#: build/templates/build/detail.html:17 +#: build/templates/build/detail.html:21 #: company/templates/company/detail_part.html:65 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:25 @@ -263,12 +263,12 @@ msgstr "" #: company/templates/company/supplier_part_base.html:50 #: company/templates/company/supplier_part_detail.html:27 #: order/templates/order/purchase_order_detail.html:26 -#: part/templates/part/detail.html:37 +#: part/templates/part/detail.html:38 msgid "Description" msgstr "" #: build/templates/build/allocate_view.html:22 -#: part/templates/part/part_base.html:112 +#: part/templates/part/part_base.html:115 msgid "On Order" msgstr "" @@ -284,65 +284,64 @@ msgstr "" msgid "Build Details" msgstr "" -#: build/templates/build/detail.html:14 +#: build/templates/build/detail.html:16 msgid "Title" msgstr "" -#: build/templates/build/detail.html:20 +#: build/templates/build/detail.html:26 #: company/templates/company/supplier_part_pricing.html:27 #: order/templates/order/order_wizard/select_parts.html:32 #: order/templates/order/purchase_order_detail.html:29 -#: stock/templates/stock/item_base.html:101 +#: stock/templates/stock/item_base.html:107 #: stock/templates/stock/stock_adjust.html:18 msgid "Quantity" msgstr "" -#: build/templates/build/detail.html:23 +#: build/templates/build/detail.html:30 msgid "Stock Source" msgstr "" -#: build/templates/build/detail.html:28 +#: build/templates/build/detail.html:35 msgid "Stock can be taken from any available location." msgstr "" -#: build/templates/build/detail.html:33 +#: build/templates/build/detail.html:41 #: order/templates/order/order_base.html:71 -#: stock/templates/stock/item_base.html:158 +#: stock/templates/stock/item_base.html:174 msgid "Status" msgstr "" -#: build/templates/build/detail.html:37 -#: stock/templates/stock/item_base.html:107 +#: build/templates/build/detail.html:47 +#: stock/templates/stock/item_base.html:114 msgid "Batch" msgstr "" -#: build/templates/build/detail.html:42 -#: company/templates/company/detail_part.html:90 +#: build/templates/build/detail.html:54 #: company/templates/company/supplier_part_base.html:47 #: company/templates/company/supplier_part_detail.html:24 -#: part/templates/part/part_base.html:81 -#: stock/templates/stock/item_base.html:131 -msgid "URL" +#: part/templates/part/detail.html:67 part/templates/part/part_base.html:84 +#: stock/templates/stock/item_base.html:142 +msgid "External Link" msgstr "" -#: build/templates/build/detail.html:46 -#: order/templates/order/order_base.html:75 +#: build/templates/build/detail.html:60 +#: order/templates/order/order_base.html:83 msgid "Created" msgstr "" -#: build/templates/build/detail.html:50 +#: build/templates/build/detail.html:66 msgid "Enough Parts?" msgstr "" -#: build/templates/build/detail.html:53 +#: build/templates/build/detail.html:69 msgid "Yes" msgstr "" -#: build/templates/build/detail.html:55 +#: build/templates/build/detail.html:71 msgid "No" msgstr "" -#: build/templates/build/detail.html:62 +#: build/templates/build/detail.html:79 msgid "Completed" msgstr "" @@ -371,7 +370,7 @@ msgstr "" msgid "Outputs" msgstr "" -#: build/templates/build/tabs.html:11 company/models.py:248 +#: build/templates/build/tabs.html:11 company/models.py:264 #: company/templates/company/tabs.html:26 order/templates/order/tabs.html:15 #: part/templates/part/tabs.html:58 stock/templates/stock/tabs.html:17 msgid "Notes" @@ -530,81 +529,81 @@ msgstr "" msgid "Delete Currency" msgstr "" -#: company/models.py:74 +#: company/models.py:76 msgid "Company name" msgstr "" -#: company/models.py:76 +#: company/models.py:78 msgid "Description of the company" msgstr "" -#: company/models.py:78 +#: company/models.py:80 msgid "Company website URL" msgstr "" -#: company/models.py:81 +#: company/models.py:83 msgid "Company address" msgstr "" -#: company/models.py:84 +#: company/models.py:86 msgid "Contact phone number" msgstr "" -#: company/models.py:86 +#: company/models.py:88 msgid "Contact email address" msgstr "" -#: company/models.py:89 +#: company/models.py:91 msgid "Point of contact" msgstr "" -#: company/models.py:91 +#: company/models.py:93 msgid "Link to external company information" msgstr "" -#: company/models.py:97 +#: company/models.py:105 msgid "Do you sell items to this company?" msgstr "" -#: company/models.py:99 +#: company/models.py:107 msgid "Do you purchase items from this company?" msgstr "" -#: company/models.py:229 +#: company/models.py:245 msgid "Select part" msgstr "" -#: company/models.py:235 +#: company/models.py:251 msgid "Select supplier" msgstr "" -#: company/models.py:238 +#: company/models.py:254 msgid "Supplier stock keeping unit" msgstr "" -#: company/models.py:240 company/templates/company/detail_part.html:81 +#: company/models.py:256 company/templates/company/detail_part.html:81 #: company/templates/company/supplier_part_base.html:53 #: company/templates/company/supplier_part_detail.html:30 msgid "Manufacturer" msgstr "" -#: company/models.py:242 +#: company/models.py:258 msgid "Manufacturer part number" msgstr "" -#: company/models.py:244 +#: company/models.py:260 msgid "URL for external supplier part link" msgstr "" -#: company/models.py:246 +#: company/models.py:262 msgid "Supplier part description" msgstr "" -#: company/models.py:250 +#: company/models.py:266 msgid "Minimum charge (e.g. stocking fee)" msgstr "" -#: company/models.py:252 +#: company/models.py:268 msgid "Part packaging" msgstr "" @@ -612,24 +611,24 @@ msgstr "" msgid "Company" msgstr "" -#: company/templates/company/company_base.html:48 +#: company/templates/company/company_base.html:50 #: company/templates/company/index.html:59 msgid "Website" msgstr "" -#: company/templates/company/company_base.html:53 +#: company/templates/company/company_base.html:57 msgid "Address" msgstr "" -#: company/templates/company/company_base.html:58 +#: company/templates/company/company_base.html:64 msgid "Phone" msgstr "" -#: company/templates/company/company_base.html:63 +#: company/templates/company/company_base.html:71 msgid "Email" msgstr "" -#: company/templates/company/company_base.html:68 +#: company/templates/company/company_base.html:78 msgid "Contact" msgstr "" @@ -637,18 +636,18 @@ msgstr "" msgid "Company Details" msgstr "" -#: company/templates/company/detail.html:13 -#: stock/templates/stock/item_base.html:125 +#: company/templates/company/detail.html:16 +#: stock/templates/stock/item_base.html:135 msgid "Customer" msgstr "" -#: company/templates/company/detail.html:17 +#: company/templates/company/detail.html:21 #: company/templates/company/index.html:46 #: company/templates/company/supplier_part_base.html:44 #: company/templates/company/supplier_part_detail.html:21 -#: order/templates/order/order_base.html:67 +#: order/templates/order/order_base.html:66 #: order/templates/order/order_wizard/select_pos.html:30 -#: stock/templates/stock/item_base.html:137 +#: stock/templates/stock/item_base.html:149 msgid "Supplier" msgstr "" @@ -676,6 +675,10 @@ msgstr "" msgid "SKU" msgstr "" +#: company/templates/company/detail_part.html:90 +msgid "Link" +msgstr "" + #: company/templates/company/detail_purchase_orders.html:8 #: company/templates/company/tabs.html:15 part/templates/part/tabs.html:43 msgid "Purchase Orders" @@ -708,8 +711,8 @@ msgstr "" msgid "ID" msgstr "" -#: company/templates/company/index.html:69 part/templates/part/category.html:81 -#: templates/navbar.html:10 templates/stats.html:7 templates/stats.html:10 +#: company/templates/company/index.html:69 part/templates/part/category.html:83 +#: templates/navbar.html:10 templates/stats.html:8 templates/stats.html:17 msgid "Parts" msgstr "" @@ -724,7 +727,7 @@ msgstr "" #: company/templates/company/supplier_part_base.html:6 #: company/templates/company/supplier_part_base.html:13 -#: stock/templates/stock/item_base.html:141 +#: stock/templates/stock/item_base.html:154 msgid "Supplier Part" msgstr "" @@ -903,7 +906,7 @@ msgid "Order notes" msgstr "" #: order/models.py:162 order/models.py:213 part/views.py:1119 -#: stock/models.py:463 +#: stock/models.py:467 msgid "Quantity must be greater than zero" msgstr "" @@ -927,7 +930,7 @@ msgstr "" msgid "Line item notes" msgstr "" -#: order/models.py:298 stock/templates/stock/item_base.html:119 +#: order/models.py:298 stock/templates/stock/item_base.html:128 msgid "Purchase Order" msgstr "" @@ -939,15 +942,15 @@ msgstr "" msgid "Number of items received" msgstr "" -#: order/templates/order/order_base.html:64 +#: order/templates/order/order_base.html:61 msgid "Purchase Order Details" msgstr "" -#: order/templates/order/order_base.html:80 +#: order/templates/order/order_base.html:89 msgid "Issued" msgstr "" -#: order/templates/order/order_base.html:86 +#: order/templates/order/order_base.html:96 #: order/templates/order/purchase_order_detail.html:31 msgid "Received" msgstr "" @@ -1213,171 +1216,171 @@ msgstr "" msgid "Select currency for price calculation" msgstr "" -#: part/models.py:62 +#: part/models.py:61 msgid "Default location for parts in this category" msgstr "" -#: part/models.py:65 +#: part/models.py:64 msgid "Default keywords for parts in this category" msgstr "" -#: part/models.py:339 +#: part/models.py:338 msgid "Part must be unique for name, IPN and revision" msgstr "" -#: part/models.py:353 +#: part/models.py:352 msgid "Part cannot be a template part if it is a variant of another part" msgstr "" -#: part/models.py:354 +#: part/models.py:353 msgid "Part cannot be a variant of another part if it is already a template" msgstr "" -#: part/models.py:358 part/templates/part/detail.html:18 +#: part/models.py:357 part/templates/part/detail.html:19 msgid "Part name" msgstr "" -#: part/models.py:362 +#: part/models.py:361 msgid "Is this part a template part?" msgstr "" -#: part/models.py:371 +#: part/models.py:370 msgid "Is this part a variant of another part?" msgstr "" -#: part/models.py:373 +#: part/models.py:372 msgid "Part description" msgstr "" -#: part/models.py:375 +#: part/models.py:374 msgid "Part keywords to improve visibility in search results" msgstr "" -#: part/models.py:380 +#: part/models.py:379 msgid "Part category" msgstr "" -#: part/models.py:382 +#: part/models.py:381 msgid "Internal Part Number" msgstr "" -#: part/models.py:384 +#: part/models.py:383 msgid "Part revision or version number" msgstr "" -#: part/models.py:386 +#: part/models.py:385 msgid "Link to extenal URL" msgstr "" -#: part/models.py:398 +#: part/models.py:397 msgid "Where is this item normally stored?" msgstr "" -#: part/models.py:442 +#: part/models.py:441 msgid "Default supplier part" msgstr "" -#: part/models.py:445 +#: part/models.py:444 msgid "Minimum allowed stock level" msgstr "" -#: part/models.py:447 +#: part/models.py:446 msgid "Stock keeping units for this part" msgstr "" -#: part/models.py:449 +#: part/models.py:448 msgid "Can this part be built from other parts?" msgstr "" -#: part/models.py:451 +#: part/models.py:450 msgid "Can this part be used to build other parts?" msgstr "" -#: part/models.py:453 +#: part/models.py:452 msgid "Does this part have tracking for unique items?" msgstr "" -#: part/models.py:455 +#: part/models.py:454 msgid "Can this part be purchased from external suppliers?" msgstr "" -#: part/models.py:457 +#: part/models.py:456 msgid "Can this part be sold to customers?" msgstr "" -#: part/models.py:459 +#: part/models.py:458 msgid "Is this part active?" msgstr "" -#: part/models.py:461 +#: part/models.py:460 msgid "Is this a virtual part, such as a software product or license?" msgstr "" -#: part/models.py:463 +#: part/models.py:462 msgid "Part notes - supports Markdown formatting" msgstr "" -#: part/models.py:465 +#: part/models.py:464 msgid "Stored BOM checksum" msgstr "" -#: part/models.py:1041 +#: part/models.py:1040 msgid "Parameter template name must be unique" msgstr "" -#: part/models.py:1046 +#: part/models.py:1045 msgid "Parameter Name" msgstr "" -#: part/models.py:1048 +#: part/models.py:1047 msgid "Parameter Units" msgstr "" -#: part/models.py:1074 +#: part/models.py:1073 msgid "Parent Part" msgstr "" -#: part/models.py:1076 +#: part/models.py:1075 msgid "Parameter Template" msgstr "" -#: part/models.py:1078 +#: part/models.py:1077 msgid "Parameter Value" msgstr "" -#: part/models.py:1102 +#: part/models.py:1101 msgid "Select parent part" msgstr "" -#: part/models.py:1111 +#: part/models.py:1110 msgid "Select part to be used in BOM" msgstr "" -#: part/models.py:1118 +#: part/models.py:1117 msgid "BOM quantity for this BOM item" msgstr "" -#: part/models.py:1121 +#: part/models.py:1120 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "" -#: part/models.py:1124 +#: part/models.py:1123 msgid "BOM item reference" msgstr "" -#: part/models.py:1127 +#: part/models.py:1126 msgid "BOM item notes" msgstr "" -#: part/models.py:1129 +#: part/models.py:1128 msgid "BOM line checksum" msgstr "" -#: part/models.py:1192 +#: part/models.py:1191 msgid "Part cannot be added to its own Bill of Materials" msgstr "" -#: part/models.py:1199 +#: part/models.py:1198 #, python-brace-format msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" msgstr "" @@ -1386,8 +1389,8 @@ msgstr "" msgid "Part Attachments" msgstr "" -#: part/templates/part/category.html:13 part/templates/part/category.html:76 -#: templates/stats.html:14 +#: part/templates/part/category.html:13 part/templates/part/category.html:78 +#: templates/stats.html:12 msgid "Part Categories" msgstr "" @@ -1395,31 +1398,31 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:34 part/templates/part/category.html:72 +#: part/templates/part/category.html:34 part/templates/part/category.html:73 msgid "Category Details" msgstr "" -#: part/templates/part/category.html:38 +#: part/templates/part/category.html:39 msgid "Category Path" msgstr "" -#: part/templates/part/category.html:43 +#: part/templates/part/category.html:44 msgid "Category Description" msgstr "" -#: part/templates/part/category.html:49 part/templates/part/detail.html:73 +#: part/templates/part/category.html:50 part/templates/part/detail.html:74 msgid "Default Location" msgstr "" -#: part/templates/part/category.html:56 part/templates/part/detail.html:50 +#: part/templates/part/category.html:57 part/templates/part/detail.html:51 msgid "Keywords" msgstr "" -#: part/templates/part/category.html:62 +#: part/templates/part/category.html:63 msgid "Subcategories" msgstr "" -#: part/templates/part/category.html:67 +#: part/templates/part/category.html:68 msgid "Parts (Including subcategories)" msgstr "" @@ -1427,115 +1430,111 @@ msgstr "" msgid "Part Details" msgstr "" -#: part/templates/part/detail.html:24 part/templates/part/part_base.html:75 +#: part/templates/part/detail.html:25 part/templates/part/part_base.html:77 msgid "IPN" msgstr "" -#: part/templates/part/detail.html:31 +#: part/templates/part/detail.html:32 msgid "Revision" msgstr "" -#: part/templates/part/detail.html:43 +#: part/templates/part/detail.html:44 msgid "Variant Of" msgstr "" -#: part/templates/part/detail.html:56 +#: part/templates/part/detail.html:57 msgid "Category" msgstr "" -#: part/templates/part/detail.html:66 -msgid "Link" -msgstr "" - -#: part/templates/part/detail.html:80 +#: part/templates/part/detail.html:81 msgid "Default Supplier" msgstr "" -#: part/templates/part/detail.html:88 +#: part/templates/part/detail.html:89 msgid "Units" msgstr "" -#: part/templates/part/detail.html:94 +#: part/templates/part/detail.html:95 msgid "Minimum Stock" msgstr "" -#: part/templates/part/detail.html:100 +#: part/templates/part/detail.html:101 msgid "Creation Date" msgstr "" -#: part/templates/part/detail.html:106 +#: part/templates/part/detail.html:107 msgid "Created By" msgstr "" -#: part/templates/part/detail.html:113 +#: part/templates/part/detail.html:114 msgid "Responsible User" msgstr "" -#: part/templates/part/detail.html:122 +#: part/templates/part/detail.html:123 msgid "Virtual" msgstr "" -#: part/templates/part/detail.html:125 +#: part/templates/part/detail.html:126 msgid "Part is virtual (not a physical part)" msgstr "" -#: part/templates/part/detail.html:127 +#: part/templates/part/detail.html:128 msgid "Part is not a virtual part" msgstr "" -#: part/templates/part/detail.html:131 +#: part/templates/part/detail.html:132 msgid "Assembly" msgstr "" -#: part/templates/part/detail.html:134 +#: part/templates/part/detail.html:135 msgid "Part can be assembled from other parts" msgstr "" -#: part/templates/part/detail.html:136 +#: part/templates/part/detail.html:137 msgid "Part cannot be assembled from other parts" msgstr "" -#: part/templates/part/detail.html:140 +#: part/templates/part/detail.html:141 msgid "Component" msgstr "" -#: part/templates/part/detail.html:143 +#: part/templates/part/detail.html:144 msgid "Part can be used in assemblies" msgstr "" -#: part/templates/part/detail.html:145 +#: part/templates/part/detail.html:146 msgid "Part cannot be used in assemblies" msgstr "" -#: part/templates/part/detail.html:149 +#: part/templates/part/detail.html:150 msgid "Trackable" msgstr "" -#: part/templates/part/detail.html:152 +#: part/templates/part/detail.html:153 msgid "Part stock is tracked by serial number" msgstr "" -#: part/templates/part/detail.html:154 +#: part/templates/part/detail.html:155 msgid "Part stock is not tracked by serial number" msgstr "" -#: part/templates/part/detail.html:158 +#: part/templates/part/detail.html:159 msgid "Purchaseable" msgstr "" -#: part/templates/part/detail.html:161 part/templates/part/detail.html:163 +#: part/templates/part/detail.html:162 part/templates/part/detail.html:164 msgid "Part can be purchased from external suppliers" msgstr "" -#: part/templates/part/detail.html:168 +#: part/templates/part/detail.html:169 msgid "Sellable" msgstr "" -#: part/templates/part/detail.html:171 +#: part/templates/part/detail.html:172 msgid "Part can be sold to customers" msgstr "" -#: part/templates/part/detail.html:173 +#: part/templates/part/detail.html:174 msgid "Part cannot be sold to customers" msgstr "" @@ -1563,23 +1562,23 @@ msgstr "" msgid "Show pricing information" msgstr "" -#: part/templates/part/part_base.html:95 +#: part/templates/part/part_base.html:98 msgid "Available Stock" msgstr "" -#: part/templates/part/part_base.html:100 +#: part/templates/part/part_base.html:103 msgid "In Stock" msgstr "" -#: part/templates/part/part_base.html:121 +#: part/templates/part/part_base.html:124 msgid "Build Status" msgstr "" -#: part/templates/part/part_base.html:125 +#: part/templates/part/part_base.html:128 msgid "Can Build" msgstr "" -#: part/templates/part/part_base.html:130 +#: part/templates/part/part_base.html:133 msgid "Underway" msgstr "" @@ -1619,7 +1618,7 @@ msgstr "" msgid "BOM" msgstr "" -#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:113 +#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:121 #: templates/navbar.html:12 msgid "Build" msgstr "" @@ -1737,67 +1736,67 @@ msgstr "" msgid "Export Bill of Materials" msgstr "" -#: part/views.py:1406 +#: part/views.py:1404 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1413 +#: part/views.py:1411 msgid "Part was deleted" msgstr "" -#: part/views.py:1422 +#: part/views.py:1420 msgid "Part Pricing" msgstr "" -#: part/views.py:1544 +#: part/views.py:1542 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1552 +#: part/views.py:1550 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1559 +#: part/views.py:1557 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1567 +#: part/views.py:1565 msgid "Create Part Parameter" msgstr "" -#: part/views.py:1617 +#: part/views.py:1615 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:1631 +#: part/views.py:1629 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:1647 +#: part/views.py:1645 msgid "Edit Part Category" msgstr "" -#: part/views.py:1682 +#: part/views.py:1680 msgid "Delete Part Category" msgstr "" -#: part/views.py:1688 +#: part/views.py:1686 msgid "Part category was deleted" msgstr "" -#: part/views.py:1696 +#: part/views.py:1694 msgid "Create new part category" msgstr "" -#: part/views.py:1747 +#: part/views.py:1745 msgid "Create BOM item" msgstr "" -#: part/views.py:1813 +#: part/views.py:1811 msgid "Edit BOM item" msgstr "" -#: part/views.py:1861 +#: part/views.py:1859 msgid "Confim BOM item deletion" msgstr "" @@ -1893,45 +1892,45 @@ msgstr "" msgid "Stock Item Notes" msgstr "" -#: stock/models.py:460 +#: stock/models.py:464 msgid "Quantity must be integer" msgstr "" -#: stock/models.py:466 +#: stock/models.py:470 #, python-brace-format msgid "Quantity must not exceed available stock quantity ({n})" msgstr "" -#: stock/models.py:469 stock/models.py:472 +#: stock/models.py:473 stock/models.py:476 msgid "Serial numbers must be a list of integers" msgstr "" -#: stock/models.py:475 +#: stock/models.py:479 msgid "Quantity does not match serial numbers" msgstr "" -#: stock/models.py:485 +#: stock/models.py:489 msgid "Serial numbers already exist: " msgstr "" -#: stock/models.py:507 +#: stock/models.py:511 msgid "Add serial number" msgstr "" -#: stock/models.py:510 +#: stock/models.py:514 #, python-brace-format msgid "Serialized {n} items" msgstr "" -#: stock/models.py:810 +#: stock/models.py:814 msgid "Tracking entry title" msgstr "" -#: stock/models.py:812 +#: stock/models.py:816 msgid "Entry notes" msgstr "" -#: stock/models.py:814 +#: stock/models.py:818 msgid "Link to external page for further information" msgstr "" @@ -1962,28 +1961,28 @@ msgstr "" msgid "This stock item was split from " msgstr "" -#: stock/templates/stock/item_base.html:85 +#: stock/templates/stock/item_base.html:88 msgid "Belongs To" msgstr "" -#: stock/templates/stock/item_base.html:90 +#: stock/templates/stock/item_base.html:94 #: stock/templates/stock/stock_adjust.html:17 msgid "Location" msgstr "" -#: stock/templates/stock/item_base.html:96 +#: stock/templates/stock/item_base.html:101 msgid "Serial Number" msgstr "" -#: stock/templates/stock/item_base.html:146 +#: stock/templates/stock/item_base.html:160 msgid "Last Updated" msgstr "" -#: stock/templates/stock/item_base.html:150 +#: stock/templates/stock/item_base.html:165 msgid "Last Stocktake" msgstr "" -#: stock/templates/stock/item_base.html:154 +#: stock/templates/stock/item_base.html:169 msgid "No stocktake performed" msgstr "" @@ -1999,29 +1998,29 @@ msgstr "" msgid "Location Details" msgstr "" -#: stock/templates/stock/location.html:41 +#: stock/templates/stock/location.html:42 msgid "Location Path" msgstr "" -#: stock/templates/stock/location.html:46 +#: stock/templates/stock/location.html:47 msgid "Location Description" msgstr "" -#: stock/templates/stock/location.html:51 +#: stock/templates/stock/location.html:52 msgid "Sublocations" msgstr "" -#: stock/templates/stock/location.html:56 -#: stock/templates/stock/location.html:70 templates/stats.html:18 -#: templates/stats.html:21 +#: stock/templates/stock/location.html:57 +#: stock/templates/stock/location.html:72 templates/stats.html:21 +#: templates/stats.html:30 msgid "Stock Items" msgstr "" -#: stock/templates/stock/location.html:61 +#: stock/templates/stock/location.html:62 msgid "Stock Details" msgstr "" -#: stock/templates/stock/location.html:65 +#: stock/templates/stock/location.html:67 #: templates/InvenTree/search_stock_location.html:6 templates/stats.html:25 msgid "Stock Locations" msgstr "" @@ -2194,30 +2193,38 @@ msgstr "" msgid "No results found" msgstr "" -#: templates/about.html:18 +#: templates/about.html:13 msgid "InvenTree Version Information" msgstr "" #: templates/about.html:21 -msgid "Version" +msgid "Instance Name" msgstr "" -#: templates/about.html:24 +#: templates/about.html:26 +msgid "InvenTree Version" +msgstr "" + +#: templates/about.html:30 msgid "Commit Hash" msgstr "" -#: templates/about.html:27 +#: templates/about.html:34 msgid "Commit Date" msgstr "" -#: templates/about.html:33 +#: templates/about.html:38 msgid "InvenTree Documentation" msgstr "" -#: templates/about.html:37 +#: templates/about.html:43 msgid "View Code on GitHub" msgstr "" +#: templates/about.html:47 +msgid "Submit Bug Report" +msgstr "" + #: templates/navbar.html:23 msgid "Admin" msgstr "" diff --git a/InvenTree/part/apps.py b/InvenTree/part/apps.py index 43f6429c19..95193a9527 100644 --- a/InvenTree/part/apps.py +++ b/InvenTree/part/apps.py @@ -1,7 +1,35 @@ from __future__ import unicode_literals +import os + +from django.db.utils import OperationalError, ProgrammingError from django.apps import AppConfig +from django.conf import settings class PartConfig(AppConfig): name = 'part' + + def ready(self): + """ + This function is called whenever the Part app is loaded. + """ + + self.generate_part_thumbnails() + + def generate_part_thumbnails(self): + from .models import Part + + print("InvenTree: Checking Part image thumbnails") + + try: + for part in Part.objects.all(): + if part.image: + url = part.image.thumbnail.name + loc = os.path.join(settings.MEDIA_ROOT, url) + + if not os.path.exists(loc): + print("InvenTree: Generating thumbnail for Part '{p}'".format(p=part.name)) + part.image.render_variations(replace=False) + except (OperationalError, ProgrammingError): + print("Could not generate Part thumbnails") diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index da0bff16b8..d751dca215 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -10,7 +10,6 @@ import os from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ValidationError from django.urls import reverse -from django.conf import settings from django.db import models, transaction from django.db.models import Sum @@ -300,9 +299,9 @@ class Part(models.Model): """ Return the URL of the image for this part """ if self.image: - return os.path.join(settings.MEDIA_URL, str(self.image.url)) + return helpers.getMediaUrl(self.image.url) else: - return os.path.join(settings.STATIC_URL, 'img/blank_image.png') + return helpers.getBlankImage() def get_thumbnail_url(self): """ @@ -310,9 +309,9 @@ class Part(models.Model): """ if self.image: - return os.path.join(settings.MEDIA_URL, str(self.image.thumbnail.url)) + return helpers.getMediaUrl(self.image.thumbnail.url) else: - return os.path.join(settings.STATIC_URL, 'img/blank_image.thumbnail.png') + return helpers.getBlankThumbnail() def validate_unique(self, exclude=None): """ Validate that a part is 'unique'. diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index b0d3b3bd62..ad989459ee 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -14,7 +14,7 @@ from .models import StockItemTracking from part.models import Part, PartCategory -from .serializers import StockItemSerializer, StockQuantitySerializer +from .serializers import StockItemSerializer from .serializers import LocationSerializer from .serializers import StockTrackingSerializer @@ -23,11 +23,12 @@ from InvenTree.helpers import str2bool, isNull from InvenTree.status_codes import StockStatus import os +from decimal import Decimal, InvalidOperation from rest_framework.serializers import ValidationError from rest_framework.views import APIView from rest_framework.response import Response -from rest_framework import generics, response, filters, permissions +from rest_framework import generics, filters, permissions class StockCategoryTree(TreeSerializer): @@ -95,144 +96,154 @@ class StockFilter(FilterSet): fields = ['quantity', 'part', 'location'] -class StockStocktake(APIView): - """ Stocktake API endpoint provides stock update of multiple items simultaneously. - The 'action' field tells the type of stock action to perform: - - stocktake: Count the stock item(s) - - remove: Remove the quantity provided from stock - - add: Add the quantity provided from stock +class StockAdjust(APIView): + """ + A generic class for handling stocktake actions. + + Subclasses exist for: + + - StockCount: count stock items + - StockAdd: add stock items + - StockRemove: remove stock items + - StockTransfer: transfer stock items """ permission_classes = [ permissions.IsAuthenticated, ] + def get_items(self, request): + """ + Return a list of items posted to the endpoint. + Will raise validation errors if the items are not + correctly formatted. + """ + + _items = [] + + if 'item' in request.data: + _items = [request.data['item']] + elif 'items' in request.data: + _items = request.data['items'] + else: + raise ValidationError({'items': 'Request must contain list of stock items'}) + + # List of validated items + self.items = [] + + for entry in _items: + + if not type(entry) == dict: + raise ValidationError({'error': 'Improperly formatted data'}) + + try: + pk = entry.get('pk', None) + item = StockItem.objects.get(pk=pk) + except (ValueError, StockItem.DoesNotExist): + raise ValidationError({'pk': 'Each entry must contain a valid pk field'}) + + try: + quantity = Decimal(str(entry.get('quantity', None))) + except (ValueError, TypeError, InvalidOperation): + raise ValidationError({'quantity': 'Each entry must contain a valid quantity field'}) + + if quantity < 0: + raise ValidationError({'quantity': 'Quantity field must not be less than zero'}) + + self.items.append({ + 'item': item, + 'quantity': quantity + }) + + self.notes = str(request.data.get('notes', '')) + + +class StockCount(StockAdjust): + """ + Endpoint for counting stock (performing a stocktake). + """ + def post(self, request, *args, **kwargs): - if 'action' not in request.data: - raise ValidationError({'action': 'Stocktake action must be provided'}) - - action = request.data['action'] - - ACTIONS = ['stocktake', 'remove', 'add'] - - if action not in ACTIONS: - raise ValidationError({'action': 'Action must be one of ' + ','.join(ACTIONS)}) - - elif 'items[]' not in request.data: - raise ValidationError({'items[]:' 'Request must contain list of items'}) - - items = [] - - # Ensure each entry is valid - for entry in request.data['items[]']: - if 'pk' not in entry: - raise ValidationError({'pk': 'Each entry must contain pk field'}) - elif 'quantity' not in entry: - raise ValidationError({'quantity': 'Each entry must contain quantity field'}) - - item = {} - try: - item['item'] = StockItem.objects.get(pk=entry['pk']) - except StockItem.DoesNotExist: - raise ValidationError({'pk': 'No matching StockItem found for pk={pk}'.format(pk=entry['pk'])}) - try: - item['quantity'] = int(entry['quantity']) - except ValueError: - raise ValidationError({'quantity': 'Quantity must be an integer'}) - - if item['quantity'] < 0: - raise ValidationError({'quantity': 'Quantity must be >= 0'}) - - items.append(item) - - # Stocktake notes - notes = '' - - if 'notes' in request.data: - notes = request.data['notes'] + self.get_items(request) n = 0 - for item in items: - quantity = int(item['quantity']) + for item in self.items: - if action == u'stocktake': - if item['item'].stocktake(quantity, request.user, notes=notes): - n += 1 - elif action == u'remove': - if item['item'].take_stock(quantity, request.user, notes=notes): - n += 1 - elif action == u'add': - if item['item'].add_stock(quantity, request.user, notes=notes): - n += 1 + if item['item'].stocktake(item['quantity'], request.user, notes=self.notes): + n += 1 return Response({'success': 'Updated stock for {n} items'.format(n=n)}) -class StockMove(APIView): - """ API endpoint for performing stock movements """ - - permission_classes = [ - permissions.IsAuthenticated, - ] +class StockAdd(StockAdjust): + """ + Endpoint for adding stock + """ def post(self, request, *args, **kwargs): + self.get_items(request) + + n = 0 + + for item in self.items: + if item['item'].add_stock(item['quantity'], request.user, notes=self.notes): + n += 1 + + return Response({"success": "Added stock for {n} items".format(n=n)}) + + +class StockRemove(StockAdjust): + """ + Endpoint for removing stock. + """ + + def post(self, request, *args, **kwargs): + + self.get_items(request) + + n = 0 + + for item in self.items: + + if item['item'].take_stock(item['quantity'], request.user, notes=self.notes): + n += 1 + + return Response({"success": "Removed stock for {n} items".format(n=n)}) + + +class StockTransfer(StockAdjust): + """ + API endpoint for performing stock movements + """ + + def post(self, request, *args, **kwargs): + + self.get_items(request) + data = request.data - if 'location' not in data: - raise ValidationError({'location': 'Destination must be specified'}) - try: - loc_id = int(data.get('location')) - except ValueError: - raise ValidationError({'location': 'Integer ID required'}) + location = StockLocation.objects.get(pk=data.get('location', None)) + except (ValueError, StockLocation.DoesNotExist): + raise ValidationError({'location': 'Valid location must be specified'}) - try: - location = StockLocation.objects.get(pk=loc_id) - except StockLocation.DoesNotExist: - raise ValidationError({'location': 'Location does not exist'}) + n = 0 - if 'stock' not in data: - raise ValidationError({'stock': 'Stock list must be specified'}) - - stock_list = data.get('stock') + for item in self.items: - if type(stock_list) is not list: - raise ValidationError({'stock': 'Stock must be supplied as a list'}) + # If quantity is not specified, move the entire stock + if item['quantity'] in [0, None]: + item['quantity'] = item['item'].quantity - if 'notes' not in data: - raise ValidationError({'notes': 'Notes field must be supplied'}) + if item['item'].move(location, self.notes, request.user, quantity=item['quantity']): + n += 1 - for item in stock_list: - try: - stock_id = int(item['pk']) - if 'quantity' in item: - quantity = int(item['quantity']) - else: - # If quantity not supplied, we'll move the entire stock - quantity = None - except ValueError: - # Ignore this one - continue - - # Ignore a zero quantity movement - if quantity <= 0: - continue - - try: - stock = StockItem.objects.get(pk=stock_id) - except StockItem.DoesNotExist: - continue - - if quantity is None: - quantity = stock.quantity - - stock.move(location, data.get('notes'), request.user, quantity=quantity) - - return Response({'success': 'Moved parts to {loc}'.format( - loc=str(location) + return Response({'success': 'Moved {n} parts to {loc}'.format( + n=n, + loc=str(location), )}) @@ -512,22 +523,6 @@ class StockList(generics.ListCreateAPIView): ] -class StockStocktakeEndpoint(generics.UpdateAPIView): - """ API endpoint for performing stocktake """ - - queryset = StockItem.objects.all() - serializer_class = StockQuantitySerializer - permission_classes = (permissions.IsAuthenticated,) - - def update(self, request, *args, **kwargs): - object = self.get_object() - object.stocktake(request.data['quantity'], request.user) - - serializer = self.get_serializer(object) - - return response.Response(serializer.data) - - class StockTrackingList(generics.ListCreateAPIView): """ API endpoint for list view of StockItemTracking objects. @@ -591,8 +586,10 @@ stock_api_urls = [ url(r'location/', include(location_endpoints)), # These JSON endpoints have been replaced (for now) with server-side form rendering - 02/06/2019 - # url(r'stocktake/?', StockStocktake.as_view(), name='api-stock-stocktake'), - # url(r'move/?', StockMove.as_view(), name='api-stock-move'), + url(r'count/?', StockCount.as_view(), name='api-stock-count'), + url(r'add/?', StockAdd.as_view(), name='api-stock-add'), + url(r'remove/?', StockRemove.as_view(), name='api-stock-remove'), + url(r'transfer/?', StockTransfer.as_view(), name='api-stock-transfer'), url(r'track/?', StockTrackingList.as_view(), name='api-stock-track'), diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 949823b83b..6c38335b3e 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -11,7 +11,7 @@ {% if item.serialized %}

{{ item.part.full_name}} # {{ item.serial }}

{% else %} -

{{ item.quantity }} × {{ item.part.full_name }}

+

{% decimal item.quantity %} × {{ item.part.full_name }}

{% endif %}

diff --git a/InvenTree/stock/test_api.py b/InvenTree/stock/test_api.py index 27207020b4..fe49547cee 100644 --- a/InvenTree/stock/test_api.py +++ b/InvenTree/stock/test_api.py @@ -63,3 +63,119 @@ class StockItemTest(APITestCase): def test_get_stock_list(self): response = self.client.get(self.list_url, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class StocktakeTest(APITestCase): + """ + Series of tests for the Stocktake API + """ + + fixtures = [ + 'category', + 'part', + 'company', + 'location', + 'supplier_part', + 'stock', + ] + + def setUp(self): + User = get_user_model() + User.objects.create_user('testuser', 'test@testing.com', 'password') + self.client.login(username='testuser', password='password') + + def doPost(self, url, data={}): + response = self.client.post(url, data=data, format='json') + + return response + + def test_action(self): + """ + Test each stocktake action endpoint, + for validation + """ + + for endpoint in ['api-stock-count', 'api-stock-add', 'api-stock-remove']: + + url = reverse(endpoint) + + data = {} + + # POST with a valid action + response = self.doPost(url, data) + self.assertContains(response, "must contain list", status_code=status.HTTP_400_BAD_REQUEST) + + data['items'] = [{ + 'no': 'aa' + }] + + # POST without a PK + response = self.doPost(url, data) + self.assertContains(response, 'must contain a valid pk', status_code=status.HTTP_400_BAD_REQUEST) + + # POST with a PK but no quantity + data['items'] = [{ + 'pk': 10 + }] + + response = self.doPost(url, data) + self.assertContains(response, 'must contain a valid pk', status_code=status.HTTP_400_BAD_REQUEST) + + data['items'] = [{ + 'pk': 1234 + }] + + response = self.doPost(url, data) + self.assertContains(response, 'must contain a valid quantity', status_code=status.HTTP_400_BAD_REQUEST) + + data['items'] = [{ + 'pk': 1234, + 'quantity': '10x0d' + }] + + response = self.doPost(url, data) + self.assertContains(response, 'must contain a valid quantity', status_code=status.HTTP_400_BAD_REQUEST) + + data['items'] = [{ + 'pk': 1234, + 'quantity': "-1.234" + }] + + response = self.doPost(url, data) + self.assertContains(response, 'must not be less than zero', status_code=status.HTTP_400_BAD_REQUEST) + + # Test with a single item + data = { + 'item': { + 'pk': 1234, + 'quantity': '10', + } + } + + response = self.doPost(url, data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_transfer(self): + """ + Test stock transfers + """ + + data = { + 'item': { + 'pk': 1234, + 'quantity': 10, + }, + 'location': 1, + 'notes': "Moving to a new location" + } + + url = reverse('api-stock-transfer') + + response = self.doPost(url, data) + self.assertContains(response, "Moved 1 parts to", status_code=status.HTTP_200_OK) + + # Now try one which will fail due to a bad location + data['location'] = 'not a location' + + response = self.doPost(url, data) + self.assertContains(response, 'Valid location must be specified', status_code=status.HTTP_400_BAD_REQUEST)