From 34ff05d66e85c6e4720a3322a83929e363743426 Mon Sep 17 00:00:00 2001 From: eeintech Date: Fri, 30 Oct 2020 16:09:27 -0500 Subject: [PATCH 01/52] Created PartCategoryParameterTemplate model and admin interface --- InvenTree/part/admin.py | 14 +++++ .../0053_partcategoryparametertemplate.py | 27 +++++++++ InvenTree/part/models.py | 60 ++++++++++++++++++- 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 InvenTree/part/migrations/0053_partcategoryparametertemplate.py diff --git a/InvenTree/part/admin.py b/InvenTree/part/admin.py index f0c9e3f233..61097d0952 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -12,6 +12,7 @@ from .models import PartCategory, Part from .models import PartAttachment, PartStar from .models import BomItem from .models import PartParameterTemplate, PartParameter +from .models import PartCategoryParameterTemplate from .models import PartTestTemplate from .models import PartSellPriceBreak @@ -269,6 +270,18 @@ class ParameterAdmin(ImportExportModelAdmin): list_display = ('part', 'template', 'data') +class PartCategoryParameterAdmin(admin.ModelAdmin): + + def get_form(self, request, obj=None, **kwargs): + """ Display only parent categories as choices for category field """ + + form = super().get_form(request, obj, **kwargs) + + form.base_fields['category'].choices = PartCategory.get_parent_categories() + + return form + + class PartSellPriceBreakAdmin(admin.ModelAdmin): class Meta: @@ -284,5 +297,6 @@ admin.site.register(PartStar, PartStarAdmin) admin.site.register(BomItem, BomItemAdmin) admin.site.register(PartParameterTemplate, ParameterTemplateAdmin) admin.site.register(PartParameter, ParameterAdmin) +admin.site.register(PartCategoryParameterTemplate, PartCategoryParameterAdmin) admin.site.register(PartTestTemplate, PartTestTemplateAdmin) admin.site.register(PartSellPriceBreak, PartSellPriceBreakAdmin) diff --git a/InvenTree/part/migrations/0053_partcategoryparametertemplate.py b/InvenTree/part/migrations/0053_partcategoryparametertemplate.py new file mode 100644 index 0000000000..e9a1e90af7 --- /dev/null +++ b/InvenTree/part/migrations/0053_partcategoryparametertemplate.py @@ -0,0 +1,27 @@ +# Generated by Django 3.0.7 on 2020-10-30 18:04 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0052_auto_20201027_1557'), + ] + + operations = [ + migrations.CreateModel( + name='PartCategoryParameterTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('default_value', models.CharField(blank=True, help_text='Default Parameter Value', max_length=500)), + ('category', models.ForeignKey(help_text='Part Category', on_delete=django.db.models.deletion.CASCADE, related_name='parameter_templates', to='part.PartCategory')), + ('parameter_template', models.ForeignKey(help_text='Parameter Template', on_delete=django.db.models.deletion.CASCADE, related_name='part_categories', to='part.PartParameterTemplate')), + ], + ), + migrations.AddConstraint( + model_name='partcategoryparametertemplate', + constraint=models.UniqueConstraint(fields=('category', 'parameter_template'), name='unique_category_parameter_template_pair'), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 715d1423eb..2278993bf7 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -12,7 +12,7 @@ from django.core.exceptions import ValidationError from django.urls import reverse from django.db import models, transaction -from django.db.models import Sum +from django.db.models import Sum, UniqueConstraint from django.db.models.functions import Coalesce from django.core.validators import MinValueValidator @@ -163,6 +163,22 @@ class PartCategory(InvenTreeTree): return category_parameters + @classmethod + def get_parent_categories(cls): + """ Return tuple list of parent (root) categories """ + + # Store parent categories (add empty label) + parent_categories = [ + ('', '-' * 10) + ] + # Get root nodes + root_categories = cls.objects.filter(level=0) + + for category in root_categories: + parent_categories.append((category.id, category.name)) + + return parent_categories + @receiver(pre_delete, sender=PartCategory, dispatch_uid='partcategory_delete_log') def before_delete_part_category(sender, instance, using, **kwargs): @@ -1550,6 +1566,48 @@ class PartParameter(models.Model): return part_parameter +class PartCategoryParameterTemplate(models.Model): + """ + A PartCategoryParameterTemplate creates a unique relationship between a PartCategory + and a PartParameterTemplate. + Multiple PartParameterTemplate instances can be associated to a PartCategory to drive + a default list of parameter templates attached to a Part instance upon creation. + + Attributes: + category: Reference to a single PartCategory object + parameter_template: Reference to a single PartParameterTemplate object + default_value: The default value for the parameter in the context of the selected + category + """ + + class Meta: + constraints = [ + UniqueConstraint(fields=['category', 'parameter_template'], + name='unique_category_parameter_template_pair') + ] + + def __str__(self): + """ String representation of a PartCategoryParameterTemplate (admin interface) """ + if self.default_value: + return f'{self.category.name} | {self.parameter_template.name} | {self.default_value}' + else: + return f'{self.category.name} | {self.parameter_template.name}' + + category = models.ForeignKey(PartCategory, + on_delete=models.CASCADE, + related_name='parameter_templates', + help_text=_('Part Category')) + + parameter_template = models.ForeignKey(PartParameterTemplate, + on_delete=models.CASCADE, + related_name='part_categories', + help_text=_('Parameter Template')) + + default_value = models.CharField(max_length=500, + blank=True, + help_text=_('Default Parameter Value')) + + class BomItem(models.Model): """ A BomItem links a part to its component items. A part can have a BOM (bill of materials) which defines From 5310ce84654102a0a1f1fe89420d0756f2966b64 Mon Sep 17 00:00:00 2001 From: eeintech Date: Fri, 30 Oct 2020 17:17:18 -0500 Subject: [PATCH 02/52] First step into managing Category Parameters to InvenTree settings --- InvenTree/InvenTree/urls.py | 1 + InvenTree/part/forms.py | 13 ++++++ InvenTree/part/urls.py | 8 ++++ InvenTree/part/views.py | 40 +++++++++++++++++++ .../InvenTree/settings/category.html | 40 +++++++++++++++++++ .../templates/InvenTree/settings/tabs.html | 3 ++ InvenTree/users/models.py | 1 + 7 files changed, 106 insertions(+) create mode 100644 InvenTree/templates/InvenTree/settings/category.html diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index d729210235..9b74aabe5f 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -75,6 +75,7 @@ settings_urls = [ url(r'^global/?', SettingsView.as_view(template_name='InvenTree/settings/global.html'), name='settings-global'), url(r'^currency/?', SettingsView.as_view(template_name='InvenTree/settings/currency.html'), name='settings-currency'), + url(r'^category/?', SettingsView.as_view(template_name='InvenTree/settings/category.html'), name='settings-category'), url(r'^part/?', SettingsView.as_view(template_name='InvenTree/settings/part.html'), name='settings-part'), url(r'^stock/?', SettingsView.as_view(template_name='InvenTree/settings/stock.html'), name='settings-stock'), url(r'^build/?', SettingsView.as_view(template_name='InvenTree/settings/build.html'), name='settings-build'), diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 52c39bf3ba..d1a3dcd74e 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -16,6 +16,7 @@ from django.utils.translation import ugettext as _ from .models import Part, PartCategory, PartAttachment from .models import BomItem from .models import PartParameterTemplate, PartParameter +from .models import PartCategoryParameterTemplate from .models import PartTestTemplate from .models import PartSellPriceBreak @@ -247,6 +248,18 @@ class EditCategoryForm(HelperForm): ] +class EditCategoryParameterTemplateForm(HelperForm): + """ Form for editing a PartParameterTemplate object """ + + class Meta: + model = PartCategoryParameterTemplate + fields = [ + 'category', + 'parameter_template', + 'default_value', + ] + + class EditBomItemForm(HelperForm): """ Form for editing a BomItem object """ diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index 4d81faa2d6..b5881fd0b4 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -74,10 +74,18 @@ part_detail_urls = [ url(r'^.*$', views.PartDetail.as_view(), name='part-detail'), ] +category_parameter_urls = [ + url(r'^new/', views.CategoryParameterTemplateCreate.as_view(), name='category-param-template-create'), + # url(r'^(?P\d+)/edit/', views.PartParameterTemplateEdit.as_view(), name='category-param-template-edit'), + # url(r'^(?P\d+)/delete/', views.PartParameterTemplateDelete.as_view(), name='category-param-template-edit'), +] + part_category_urls = [ url(r'^edit/?', views.CategoryEdit.as_view(), name='category-edit'), url(r'^delete/?', views.CategoryDelete.as_view(), name='category-delete'), + url(r'^template/', include(category_parameter_urls)), + url(r'^parametric/?', views.CategoryParametric.as_view(), name='category-parametric'), url(r'^.*$', views.CategoryDetail.as_view(), name='category-detail'), ] diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 0b96addc84..d13f2223b1 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -23,6 +23,7 @@ from decimal import Decimal, InvalidOperation from .models import PartCategory, Part, PartAttachment from .models import PartParameterTemplate, PartParameter +from .models import PartCategoryParameterTemplate from .models import BomItem from .models import match_part_names from .models import PartTestTemplate @@ -2137,6 +2138,45 @@ class CategoryCreate(AjaxCreateView): return initials +class CategoryParameterTemplateCreate(AjaxCreateView): + """ View for creating a new PartCategoryParameterTemplate """ + + role_required = 'part.add' + + model = PartCategoryParameterTemplate + form_class = part_forms.EditCategoryParameterTemplateForm + ajax_form_title = _('Create Category Parameter Template') + + def get_initial(self): + """ Get initial data for Category """ + initials = super().get_initial().copy() + + category_id = self.kwargs.get('pk', None) + + if category_id: + try: + initials['category'] = PartCategory.objects.get(pk=category_id) + except (PartCategory.DoesNotExist, ValueError): + pass + + return initials + + def get_form(self): + """ Create a form to upload a new CategoryParameterTemplate + - Hide the 'category' field (parent part) + - Display parameter templates which are not yet related + """ + + form = super(AjaxCreateView, self).get_form() + + form.fields['category'].widget = HiddenInput() + + if form.is_valid(): + form.cleaned_data['category'] = self.kwargs.get('pk', None) + + return form + + class BomItemDetail(InvenTreeRoleMixin, DetailView): """ Detail view for BomItem """ context_object_name = 'item' diff --git a/InvenTree/templates/InvenTree/settings/category.html b/InvenTree/templates/InvenTree/settings/category.html new file mode 100644 index 0000000000..b643af53dd --- /dev/null +++ b/InvenTree/templates/InvenTree/settings/category.html @@ -0,0 +1,40 @@ +{% extends "InvenTree/settings/settings.html" %} +{% load i18n %} + +{% block tabs %} +{% include "InvenTree/settings/tabs.html" with tab='part' %} +{% endblock %} + +{% block subtitle %} +{% trans "Category Settings" %} +{% endblock %} + +{% block settings %} + +

{% trans "Category Parameter Templates" %}

+ +
{% trans "Category: XXX (id = 1)" %}
+ +
+ +
+ + +
+ +{% endblock %} + +{% block js_ready %} +{{ block.super }} + + $("#new-param").click(function() { + launchModalForm("{% url 'category-param-template-create' 1 %}", { + success: function() { + $("#param-table").bootstrapTable('refresh'); + }, + }); + }); + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/settings/tabs.html b/InvenTree/templates/InvenTree/settings/tabs.html index d104908c49..6991b54b95 100644 --- a/InvenTree/templates/InvenTree/settings/tabs.html +++ b/InvenTree/templates/InvenTree/settings/tabs.html @@ -18,6 +18,9 @@ {% trans "Currency" %} + + {% trans "Categories" %} + {% trans "Parts" %} diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index d3c713d07d..7e65ba248e 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -57,6 +57,7 @@ class RuleSet(models.Model): 'part_parttesttemplate', 'part_partparametertemplate', 'part_partparameter', + 'part_partcategoryparametertemplate', ], 'stock': [ 'stock_stockitem', From 3e5d8d2b2d23746795268beaafe28df5dbdb0c6d Mon Sep 17 00:00:00 2001 From: eeintech Date: Sat, 31 Oct 2020 08:35:47 -0500 Subject: [PATCH 03/52] Added form to select category in settings and update context data --- InvenTree/InvenTree/forms.py | 34 +++++++++++++++ InvenTree/InvenTree/urls.py | 5 ++- InvenTree/InvenTree/views.py | 41 ++++++++++++++++++- InvenTree/part/models.py | 5 +++ .../InvenTree/settings/category.html | 23 +++++++---- 5 files changed, 98 insertions(+), 10 deletions(-) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 80df8914d3..707c0c5c79 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -12,6 +12,7 @@ from crispy_forms.layout import Layout, Field from crispy_forms.bootstrap import PrependedText, AppendedText, PrependedAppendedText, StrictButton, Div from django.contrib.auth.models import User from common.models import ColorTheme +from part.models import PartCategory class HelperForm(forms.ModelForm): @@ -195,3 +196,36 @@ class ColorThemeSelectForm(forms.ModelForm): css_class='row', ), ) + + +class SettingCategorySelectForm(forms.ModelForm): + """ Form for setting category settings """ + + name = forms.ChoiceField(choices=(), required=False) + + class Meta: + model = PartCategory + fields = [ + 'name' + ] + + def __init__(self, *args, **kwargs): + super(SettingCategorySelectForm, self).__init__(*args, **kwargs) + + # Populate category choices + self.fields['name'].choices = PartCategory.get_parent_categories() + + self.helper = FormHelper() + # Form rendering + self.helper.form_show_labels = False + self.helper.layout = Layout( + Div( + Div(Field('name'), + css_class='col-sm-6', + style='width: 200px;'), + Div(StrictButton(_('Select Category'), css_class='btn btn-primary', type='submit'), + css_class='col-sm-6', + style='width: auto;'), + css_class='row', + ), + ) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 9b74aabe5f..8006d633b5 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -36,7 +36,8 @@ from django.views.generic.base import RedirectView from rest_framework.documentation import include_docs_urls from .views import IndexView, SearchView, DatabaseStatsView -from .views import SettingsView, EditUserView, SetPasswordView, ColorThemeSelectView +from .views import SettingsView, EditUserView, SetPasswordView +from .views import ColorThemeSelectView, SettingCategorySelectView from .views import DynamicJsView from common.views import SettingEdit @@ -75,7 +76,7 @@ settings_urls = [ url(r'^global/?', SettingsView.as_view(template_name='InvenTree/settings/global.html'), name='settings-global'), url(r'^currency/?', SettingsView.as_view(template_name='InvenTree/settings/currency.html'), name='settings-currency'), - url(r'^category/?', SettingsView.as_view(template_name='InvenTree/settings/category.html'), name='settings-category'), + url(r'^category/?', SettingCategorySelectView.as_view(), name='settings-category'), url(r'^part/?', SettingsView.as_view(template_name='InvenTree/settings/part.html'), name='settings-part'), url(r'^stock/?', SettingsView.as_view(template_name='InvenTree/settings/stock.html'), name='settings-stock'), url(r'^build/?', SettingsView.as_view(template_name='InvenTree/settings/build.html'), name='settings-build'), diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 57f80f1be7..465b0dc194 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -24,7 +24,8 @@ from stock.models import StockLocation, StockItem from common.models import InvenTreeSetting, ColorTheme from users.models import check_user_role, RuleSet -from .forms import DeleteForm, EditUserForm, SetPasswordForm, ColorThemeSelectForm +from .forms import DeleteForm, EditUserForm, SetPasswordForm +from .forms import ColorThemeSelectForm, SettingCategorySelectForm from .helpers import str2bool from rest_framework import views @@ -775,6 +776,44 @@ class ColorThemeSelectView(FormView): return self.form_invalid(form) +class SettingCategorySelectView(FormView): + """ View for selecting categories in settings """ + + form_class = SettingCategorySelectForm + success_url = reverse_lazy('settings-category') + template_name = "InvenTree/settings/category.html" + + def get_initial(self): + """ Set category selection """ + + initial = super(SettingCategorySelectView, self).get_initial() + + category = self.request.GET.get('category', None) + if category: + initial['name'] = category + + return initial + + def post(self, request, *args, **kwargs): + """ Handle POST request (which contains category selection). + + Pass the selected category to the page template + """ + + form = self.get_form() + + if form.is_valid(): + category = form.cleaned_data['name'] + + context = self.get_context_data() + + context['category'] = category + + return super(SettingCategorySelectView, self).render_to_response(context) + + return self.form_invalid(form) + + class DatabaseStatsView(AjaxView): """ View for displaying database statistics """ diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 2278993bf7..37788ae56a 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -179,6 +179,11 @@ class PartCategory(InvenTreeTree): return parent_categories + def get_parameter_templates(self): + """ Return parameter templates associated to category """ + + return PartCategoryParameterTemplate.objects.filter(category=self.id) + @receiver(pre_delete, sender=PartCategory, dispatch_uid='partcategory_delete_log') def before_delete_part_category(sender, instance, using, **kwargs): diff --git a/InvenTree/templates/InvenTree/settings/category.html b/InvenTree/templates/InvenTree/settings/category.html index b643af53dd..3dc702adc1 100644 --- a/InvenTree/templates/InvenTree/settings/category.html +++ b/InvenTree/templates/InvenTree/settings/category.html @@ -2,7 +2,7 @@ {% load i18n %} {% block tabs %} -{% include "InvenTree/settings/tabs.html" with tab='part' %} +{% include "InvenTree/settings/tabs.html" with tab='category' %} {% endblock %} {% block subtitle %} @@ -11,9 +11,16 @@ {% block settings %} -

{% trans "Category Parameter Templates" %}

+
+ {% csrf_token %} + {% load crispy_forms_tags %} + {% crispy form %} +
-
{% trans "Category: XXX (id = 1)" %}
+{% if category %} +
+ +

{% trans "Category Parameter Templates" %}

"; + var bDel = ""; + + var html = value + html += "
" + bEdit + bDel + "
"; + + return html; + } + } ] }); @@ -72,5 +81,29 @@ }); }); + $("#param-table").on('click', '.template-edit', function() { + var button = $(this); + + var url = "/part/category/{{ category }}/parameters/" + button.attr('pk') + "/edit/"; + + launchModalForm(url, { + success: function() { + $("#param-table").bootstrapTable('refresh'); + } + }); + }); + + $("#param-table").on('click', '.template-delete', function() { + var button = $(this); + + var url = "/part/category/{{ category }}/parameters/" + button.attr('pk') + "/delete/"; + + launchModalForm(url, { + success: function() { + $("#param-table").bootstrapTable('refresh'); + } + }); + }); + {% endif %} {% endblock %} From 978b5f869da1ccf7ff836e620846a105e53bf1e3 Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 2 Nov 2020 12:20:29 -0500 Subject: [PATCH 06/52] Added checkbox to add parameter template to all categories --- InvenTree/InvenTree/forms.py | 10 +++----- InvenTree/part/forms.py | 5 ++++ InvenTree/part/models.py | 5 +--- InvenTree/part/views.py | 47 +++++++++++++++++++++++++++++++++--- 4 files changed, 53 insertions(+), 14 deletions(-) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 707c0c5c79..43454374ac 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -201,7 +201,8 @@ class ColorThemeSelectForm(forms.ModelForm): class SettingCategorySelectForm(forms.ModelForm): """ Form for setting category settings """ - name = forms.ChoiceField(choices=(), required=False) + name = forms.ChoiceField(choices=[('', '-' * 10)] + PartCategory.get_parent_categories(), + required=False) class Meta: model = PartCategory @@ -212,9 +213,6 @@ class SettingCategorySelectForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(SettingCategorySelectForm, self).__init__(*args, **kwargs) - # Populate category choices - self.fields['name'].choices = PartCategory.get_parent_categories() - self.helper = FormHelper() # Form rendering self.helper.form_show_labels = False @@ -222,10 +220,10 @@ class SettingCategorySelectForm(forms.ModelForm): Div( Div(Field('name'), css_class='col-sm-6', - style='width: 200px;'), + style='width: auto;'), Div(StrictButton(_('Select Category'), css_class='btn btn-primary', type='submit'), css_class='col-sm-6', - style='width: auto;'), + style='width: auto; padding-left: 0;'), css_class='row', ), ) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 81a75a22fb..f553229b5c 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -251,12 +251,17 @@ class EditCategoryForm(HelperForm): class EditCategoryParameterTemplateForm(HelperForm): """ Form for editing a PartCategoryParameterTemplate object """ + add_to_all_categories = forms.BooleanField(required=False, + initial=False, + help_text=_('Add parameter template to all categories')) + class Meta: model = PartCategoryParameterTemplate fields = [ 'category', 'parameter_template', 'default_value', + 'add_to_all_categories', ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 37788ae56a..55eef73018 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -167,13 +167,10 @@ class PartCategory(InvenTreeTree): def get_parent_categories(cls): """ Return tuple list of parent (root) categories """ - # Store parent categories (add empty label) - parent_categories = [ - ('', '-' * 10) - ] # Get root nodes root_categories = cls.objects.filter(level=0) + parent_categories = [] for category in root_categories: parent_categories.append((category.id, category.name)) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 09eb8b8a4f..e913bda57d 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals from django.core.exceptions import ValidationError from django.db import transaction +from django.db.utils import IntegrityError from django.shortcuts import get_object_or_404 from django.shortcuts import HttpResponseRedirect from django.utils.translation import gettext_lazy as _ @@ -2178,7 +2179,7 @@ class CategoryParameterTemplateCreate(AjaxCreateView): # Get category category = self.get_initial()['category'] - # Get existing related parts + # Get existing parameter templates parameters = [template.parameter_template.pk for template in category.get_parameter_templates()] @@ -2188,13 +2189,51 @@ class CategoryParameterTemplateCreate(AjaxCreateView): if (choice[0] not in parameters): updated_choices.append(choice) - # Update choices for related part + # Update choices for parameter templates form.fields['parameter_template'].choices = updated_choices except KeyError: pass return form + def post(self, request, *args, **kwargs): + """ Capture the POST request + + - If the add_to_all_categories object is set, link parameter template to + all categories + """ + + form = self.get_form() + + valid = form.is_valid() + + if valid: + all_categories = form.cleaned_data['add_to_all_categories'] + + if all_categories: + selected_category = int(self.kwargs.get('pk', 0)) + parameter_template = form.cleaned_data['parameter_template'] + default_value = form.cleaned_data['default_value'] + + # Add parameter template and default value to all categories + for category_id, category_name in PartCategory.get_parent_categories(): + # Change category_id type to integer + category_id = int(category_id) + # Skip selected category (will be processed in the post call) + if category_id != selected_category: + # Get category + category = PartCategory.objects.get(pk=category_id) + try: + cat_template = PartCategoryParameterTemplate.objects.create(category=category, + parameter_template=parameter_template, + default_value=default_value) + cat_template.save() + except IntegrityError: + # Parameter template is already linked to category + pass + + return super().post(request, *args, **kwargs) + class CategoryParameterTemplateEdit(AjaxUpdateView): """ View for editing a PartCategoryParameterTemplate """ @@ -2230,7 +2269,7 @@ class CategoryParameterTemplateEdit(AjaxUpdateView): # Get category category = PartCategory.objects.get(pk=self.kwargs.get('pk', None)) - # Get existing related parts + # Get existing parameter templates parameters = [template.parameter_template.pk for template in category.get_parameter_templates()] @@ -2240,7 +2279,7 @@ class CategoryParameterTemplateEdit(AjaxUpdateView): if (choice[0] not in parameters): updated_choices.append(choice) - # Update choices for related part + # Update choices for parameter templates form.fields['parameter_template'].choices = updated_choices except KeyError: pass From 34b784d1e47b70627c374e3724e019d93d0f3f1b Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 2 Nov 2020 13:14:31 -0500 Subject: [PATCH 07/52] Added setting, checkbox (PartCreateView only) and hook to create part parameters from category templates --- InvenTree/common/models.py | 7 +++++++ InvenTree/part/forms.py | 6 ++++++ InvenTree/part/views.py | 18 ++++++++++++++++++ .../templates/InvenTree/settings/part.html | 1 + 4 files changed, 32 insertions(+) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 0d3afbe226..9b52a89eba 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -85,6 +85,13 @@ class InvenTreeSetting(models.Model): 'validator': bool }, + 'PART_CATEGORY_PARAMETERS': { + 'name': _('Create Parameters From Category Templates'), + 'description': _('Automatically create part parameters from category templates'), + 'default': False, + 'validator': bool + }, + 'BUILDORDER_REFERENCE_PREFIX': { 'name': _('Build Order Reference Prefix'), 'description': _('Prefix value for build order reference'), diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index f553229b5c..dd41a5b583 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -186,6 +186,11 @@ class EditPartForm(HelperForm): help_text=_('Confirm part creation'), widget=forms.HiddenInput()) + category_templates = forms.BooleanField(required=False, + initial=False, + help_text=_('Create parameters from category templates'), + widget=forms.HiddenInput()) + class Meta: model = Part fields = [ @@ -193,6 +198,7 @@ class EditPartForm(HelperForm): 'parameters_copy', 'confirm_creation', 'category', + 'category_templates', 'name', 'IPN', 'description', diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index e913bda57d..e17f7da7f0 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -555,6 +555,9 @@ class PartCreate(AjaxCreateView): # Hide the default_supplier field (there are no matching supplier parts yet!) form.fields['default_supplier'].widget = HiddenInput() + # Force display of the 'category_templates' widget + form.fields['category_templates'].widget = CheckboxInput() + return form def post(self, request, *args, **kwargs): @@ -607,6 +610,18 @@ class PartCreate(AjaxCreateView): except AttributeError: pass + # Create part parameters + category_templates = form.cleaned_data['category_templates'] + if category_templates: + # Get category parent + category = form.cleaned_data['category'].get_root() + + for template in category.get_parameter_templates(): + PartParameter.create(part=part, + template=template.parameter_template, + data=template.default_value, + save=True) + return self.renderJsonResponse(request, form, data, context=context) def get_initial(self): @@ -630,6 +645,9 @@ class PartCreate(AjaxCreateView): if label in self.request.GET: initials[label] = self.request.GET.get(label) + # Automatically create part parameters from category templates + initials['category_templates'] = str2bool(InvenTreeSetting.get_setting('PART_CATEGORY_PARAMETERS', False)) + return initials diff --git a/InvenTree/templates/InvenTree/settings/part.html b/InvenTree/templates/InvenTree/settings/part.html index 19578ba858..495526c94a 100644 --- a/InvenTree/templates/InvenTree/settings/part.html +++ b/InvenTree/templates/InvenTree/settings/part.html @@ -18,6 +18,7 @@ {% include "InvenTree/settings/setting.html" with key="PART_COPY_BOM" %} {% include "InvenTree/settings/setting.html" with key="PART_COPY_PARAMETERS" %} {% include "InvenTree/settings/setting.html" with key="PART_COPY_TESTS" %} + {% include "InvenTree/settings/setting.html" with key="PART_CATEGORY_PARAMETERS" %} From 43fab8a8b379d0a2a63ebf0114fa5244ec86d2fb Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 2 Nov 2020 13:28:34 -0500 Subject: [PATCH 08/52] Backtracked on setting category choices (fixed failed migration) --- InvenTree/InvenTree/forms.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 43454374ac..a0fcd9979d 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -201,8 +201,7 @@ class ColorThemeSelectForm(forms.ModelForm): class SettingCategorySelectForm(forms.ModelForm): """ Form for setting category settings """ - name = forms.ChoiceField(choices=[('', '-' * 10)] + PartCategory.get_parent_categories(), - required=False) + name = forms.ChoiceField(required=False) class Meta: model = PartCategory @@ -213,6 +212,9 @@ class SettingCategorySelectForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(SettingCategorySelectForm, self).__init__(*args, **kwargs) + # Populate category choices + self.fields['name'].choices = [('', '-' * 10)] + PartCategory.get_parent_categories() + self.helper = FormHelper() # Form rendering self.helper.form_show_labels = False From 6320384ecb9efe6b2cf147418500b961bd9d43ee Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 2 Nov 2020 15:05:37 -0500 Subject: [PATCH 09/52] Fixed category parameter template edit form --- InvenTree/part/forms.py | 3 ++- InvenTree/part/views.py | 13 +++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index dd41a5b583..94be763b2d 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -188,7 +188,8 @@ class EditPartForm(HelperForm): category_templates = forms.BooleanField(required=False, initial=False, - help_text=_('Create parameters from category templates'), + help_text=_('Create parameters based on default category templates'), + label=_('Copy category parameter templates'), widget=forms.HiddenInput()) class Meta: diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index e17f7da7f0..2450777cd9 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -2168,7 +2168,7 @@ class CategoryParameterTemplateCreate(AjaxCreateView): def get_initial(self): """ Get initial data for Category """ - initials = super().get_initial().copy() + initials = super().get_initial() category_id = self.kwargs.get('pk', None) @@ -2194,7 +2194,7 @@ class CategoryParameterTemplateCreate(AjaxCreateView): form.cleaned_data['category'] = self.kwargs.get('pk', None) try: - # Get category + # Get selected category category = self.get_initial()['category'] # Get existing parameter templates @@ -2284,12 +2284,15 @@ class CategoryParameterTemplateEdit(AjaxUpdateView): form.cleaned_data['category'] = self.kwargs.get('pk', None) try: - # Get category + # Get selected category category = PartCategory.objects.get(pk=self.kwargs.get('pk', None)) + # Get selected template + selected_template = self.get_object().parameter_template # Get existing parameter templates parameters = [template.parameter_template.pk - for template in category.get_parameter_templates()] + for template in category.get_parameter_templates() + if template.parameter_template.pk != selected_template.pk] # Exclude templates already linked to category updated_choices = [] @@ -2299,6 +2302,8 @@ class CategoryParameterTemplateEdit(AjaxUpdateView): # Update choices for parameter templates form.fields['parameter_template'].choices = updated_choices + # Set initial choice to current template + form.fields['parameter_template'].initial = selected_template except KeyError: pass From 13a07be7285e381acd6e50323b323acdc73ce0b7 Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 2 Nov 2020 15:35:54 -0500 Subject: [PATCH 10/52] Added PartCategoryParameterTemplate tests --- InvenTree/part/fixtures/params.yaml | 19 +++++++++++++++++-- InvenTree/part/test_param.py | 20 +++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/InvenTree/part/fixtures/params.yaml b/InvenTree/part/fixtures/params.yaml index 121bb79074..2dfcada698 100644 --- a/InvenTree/part/fixtures/params.yaml +++ b/InvenTree/part/fixtures/params.yaml @@ -18,7 +18,7 @@ name: Thickness units: mm -# And some parameters (requires part.yaml) +# Add some parameters to parts (requires part.yaml) - model: part.PartParameter pk: 1 fields: @@ -31,4 +31,19 @@ fields: part: 2 template: 1 - data: 12 \ No newline at end of file + data: 12 + +# Add some template parameters to categories (requires category.yaml) +- model: part.PartCategoryParameterTemplate + pk: 1 + fields: + category: 7 + parameter_template: 1 + default_value: '2.8' + +- model: part.PartCategoryParameterTemplate + pk: 3 + fields: + category: 7 + parameter_template: 3 + default_value: '0.5' diff --git a/InvenTree/part/test_param.py b/InvenTree/part/test_param.py index 6876a2b5df..2bd3be2478 100644 --- a/InvenTree/part/test_param.py +++ b/InvenTree/part/test_param.py @@ -6,7 +6,9 @@ from __future__ import unicode_literals from django.test import TestCase import django.core.exceptions as django_exceptions +from .models import PartCategory from .models import PartParameter, PartParameterTemplate +from .models import PartCategoryParameterTemplate class TestParams(TestCase): @@ -24,7 +26,10 @@ class TestParams(TestCase): self.assertEquals(str(t1), 'Length (mm)') p1 = PartParameter.objects.get(pk=1) - self.assertEqual(str(p1), "M2x4 LPHS : Length = 4mm") + self.assertEqual(str(p1), 'M2x4 LPHS : Length = 4mm') + + c1 = PartCategoryParameterTemplate.objects.get(pk=1) + self.assertEqual(str(c1), 'Mechanical | Length | 2.8') def test_validate(self): @@ -40,3 +45,16 @@ class TestParams(TestCase): t3 = PartParameterTemplate(name='aBcde', units='dd') t3.full_clean() t3.save() + + n = PartCategoryParameterTemplate.objects.all().count() + self.assertEqual(n, 2) + + parent_category = PartCategory.objects.get(pk=8).get_root() + self.assertEqual(parent_category.pk, 7) + + c1 = PartCategoryParameterTemplate(category=parent_category, + parameter_template=t1, + default_value='xyz') + c1.save() + + self.assertEqual(n + 1, PartCategoryParameterTemplate.objects.filter(category=7).count()) From 6b702ef676458b99d997a11b229b23408f2aa114 Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 3 Nov 2020 07:27:51 -0500 Subject: [PATCH 11/52] Manually merged part migrations --- .../migrations/0052_auto_20201027_1557.py | 19 ------------------- InvenTree/part/migrations/0052_partrelated.py | 7 ++++++- .../migrations/0053_merge_20201103_1028.py | 14 -------------- .../0053_partcategoryparametertemplate.py | 2 +- 4 files changed, 7 insertions(+), 35 deletions(-) delete mode 100644 InvenTree/part/migrations/0052_auto_20201027_1557.py delete mode 100644 InvenTree/part/migrations/0053_merge_20201103_1028.py diff --git a/InvenTree/part/migrations/0052_auto_20201027_1557.py b/InvenTree/part/migrations/0052_auto_20201027_1557.py deleted file mode 100644 index 94dbcac06e..0000000000 --- a/InvenTree/part/migrations/0052_auto_20201027_1557.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.0.7 on 2020-10-27 04:57 - -import InvenTree.fields -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('part', '0051_bomitem_optional'), - ] - - operations = [ - migrations.AlterField( - model_name='part', - name='link', - field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True), - ), - ] diff --git a/InvenTree/part/migrations/0052_partrelated.py b/InvenTree/part/migrations/0052_partrelated.py index a8672ba7dc..7f9dc896a5 100644 --- a/InvenTree/part/migrations/0052_partrelated.py +++ b/InvenTree/part/migrations/0052_partrelated.py @@ -2,7 +2,7 @@ from django.db import migrations, models import django.db.models.deletion - +import InvenTree.fields class Migration(migrations.Migration): @@ -19,4 +19,9 @@ class Migration(migrations.Migration): ('part_2', models.ForeignKey(help_text='Select Related Part', on_delete=django.db.models.deletion.DO_NOTHING, related_name='related_parts_2', to='part.Part')), ], ), + migrations.AlterField( + model_name='part', + name='link', + field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True), + ), ] diff --git a/InvenTree/part/migrations/0053_merge_20201103_1028.py b/InvenTree/part/migrations/0053_merge_20201103_1028.py deleted file mode 100644 index d42595675a..0000000000 --- a/InvenTree/part/migrations/0053_merge_20201103_1028.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 3.0.7 on 2020-11-03 10:28 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('part', '0052_auto_20201027_1557'), - ('part', '0052_partrelated'), - ] - - operations = [ - ] diff --git a/InvenTree/part/migrations/0053_partcategoryparametertemplate.py b/InvenTree/part/migrations/0053_partcategoryparametertemplate.py index e9a1e90af7..6f2809af12 100644 --- a/InvenTree/part/migrations/0053_partcategoryparametertemplate.py +++ b/InvenTree/part/migrations/0053_partcategoryparametertemplate.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('part', '0052_auto_20201027_1557'), + ('part', '0052_partrelated'), ] operations = [ From 72b5a105f848a2bb1cbfdc914aa396c69536356a Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 3 Nov 2020 14:45:53 -0500 Subject: [PATCH 12/52] Made all categories accessible for parameter templates configuration --- InvenTree/InvenTree/forms.py | 11 ++++------- InvenTree/InvenTree/views.py | 6 ++---- .../templates/InvenTree/settings/category.html | 18 ++++++++++++------ 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index a0fcd9979d..3038a1cd7f 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -201,28 +201,25 @@ class ColorThemeSelectForm(forms.ModelForm): class SettingCategorySelectForm(forms.ModelForm): """ Form for setting category settings """ - name = forms.ChoiceField(required=False) + category = forms.ModelChoiceField(queryset=PartCategory.objects.all()) class Meta: model = PartCategory fields = [ - 'name' + 'category' ] def __init__(self, *args, **kwargs): super(SettingCategorySelectForm, self).__init__(*args, **kwargs) - # Populate category choices - self.fields['name'].choices = [('', '-' * 10)] + PartCategory.get_parent_categories() - self.helper = FormHelper() # Form rendering self.helper.form_show_labels = False self.helper.layout = Layout( Div( - Div(Field('name'), + Div(Field('category'), css_class='col-sm-6', - style='width: auto;'), + style='width: 30%;'), Div(StrictButton(_('Select Category'), css_class='btn btn-primary', type='submit'), css_class='col-sm-6', style='width: auto; padding-left: 0;'), diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index c59b0cdcdd..e61e2d497c 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -765,7 +765,7 @@ class SettingCategorySelectView(FormView): category = self.request.GET.get('category', None) if category: - initial['name'] = category + initial['category'] = category return initial @@ -778,11 +778,9 @@ class SettingCategorySelectView(FormView): form = self.get_form() if form.is_valid(): - category = form.cleaned_data['name'] - context = self.get_context_data() - context['category'] = category + context['category'] = form.cleaned_data['category'] return super(SettingCategorySelectView, self).render_to_response(context) diff --git a/InvenTree/templates/InvenTree/settings/category.html b/InvenTree/templates/InvenTree/settings/category.html index d5e27d79a7..d17efd9c9a 100644 --- a/InvenTree/templates/InvenTree/settings/category.html +++ b/InvenTree/templates/InvenTree/settings/category.html @@ -14,7 +14,9 @@
{% csrf_token %} {% load crispy_forms_tags %} - {% crispy form %} +
+ {% crispy form %} +
{% if category %} @@ -35,10 +37,14 @@ {% block js_ready %} {{ block.super }} -{% if category %} + $(document).ready(function() { + attachSelect('#category-select'); + }); + +{% if category %} $("#param-table").inventreeTable({ - url: "{% url 'api-part-category-parameters' category %}", + url: "{% url 'api-part-category-parameters' category.pk %}", queryParams: { ordering: 'name', }, @@ -74,7 +80,7 @@ $("#new-param").click(function() { - launchModalForm("{% url 'category-param-template-create' category %}", { + launchModalForm("{% url 'category-param-template-create' category.pk %}", { success: function() { $("#param-table").bootstrapTable('refresh'); }, @@ -84,7 +90,7 @@ $("#param-table").on('click', '.template-edit', function() { var button = $(this); - var url = "/part/category/{{ category }}/parameters/" + button.attr('pk') + "/edit/"; + var url = "/part/category/{{ category.pk }}/parameters/" + button.attr('pk') + "/edit/"; launchModalForm(url, { success: function() { @@ -96,7 +102,7 @@ $("#param-table").on('click', '.template-delete', function() { var button = $(this); - var url = "/part/category/{{ category }}/parameters/" + button.attr('pk') + "/delete/"; + var url = "/part/category/{{ category.pk }}/parameters/" + button.attr('pk') + "/delete/"; launchModalForm(url, { success: function() { From 5a5a36083e1b2be47f3f814d7155178df8c953ad Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 3 Nov 2020 16:54:46 -0500 Subject: [PATCH 13/52] Finalized implementation when creating new part --- InvenTree/part/forms.py | 17 ++++++++---- InvenTree/part/test_param.py | 5 ++-- InvenTree/part/views.py | 54 +++++++++++++++++++++++++----------- 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 4e6413b34f..47b7ac21bc 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -205,11 +205,15 @@ class EditPartForm(HelperForm): help_text=_('Confirm part creation'), widget=forms.HiddenInput()) - category_templates = forms.BooleanField(required=False, - initial=False, - help_text=_('Create parameters based on default category templates'), - label=_('Copy category parameter templates'), - widget=forms.HiddenInput()) + selected_category_templates = forms.BooleanField(required=False, + initial=False, + label=_('Include selected category parameter templates'), + widget=forms.HiddenInput()) + + parent_category_templates = forms.BooleanField(required=False, + initial=False, + label=_('Include parent category parameter templates'), + widget=forms.HiddenInput()) class Meta: model = Part @@ -218,7 +222,8 @@ class EditPartForm(HelperForm): 'parameters_copy', 'confirm_creation', 'category', - 'category_templates', + 'selected_category_templates', + 'parent_category_templates', 'name', 'IPN', 'description', diff --git a/InvenTree/part/test_param.py b/InvenTree/part/test_param.py index 2bd3be2478..ad0f5ed3b6 100644 --- a/InvenTree/part/test_param.py +++ b/InvenTree/part/test_param.py @@ -49,10 +49,9 @@ class TestParams(TestCase): n = PartCategoryParameterTemplate.objects.all().count() self.assertEqual(n, 2) - parent_category = PartCategory.objects.get(pk=8).get_root() - self.assertEqual(parent_category.pk, 7) + category = PartCategory.objects.get(pk=7) - c1 = PartCategoryParameterTemplate(category=parent_category, + c1 = PartCategoryParameterTemplate(category=category, parameter_template=t1, default_value='xyz') c1.save() diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 099dd3c515..857cf94106 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -638,8 +638,9 @@ class PartCreate(AjaxCreateView): # Hide the default_supplier field (there are no matching supplier parts yet!) form.fields['default_supplier'].widget = HiddenInput() - # Force display of the 'category_templates' widget - form.fields['category_templates'].widget = CheckboxInput() + # Display category templates widgets + form.fields['selected_category_templates'].widget = CheckboxInput() + form.fields['parent_category_templates'].widget = CheckboxInput() return form @@ -693,17 +694,40 @@ class PartCreate(AjaxCreateView): except AttributeError: pass - # Create part parameters - category_templates = form.cleaned_data['category_templates'] + # Store templates added to part + template_list = [] + + # Create part parameters for selected category + category_templates = form.cleaned_data['selected_category_templates'] if category_templates: - # Get category parent + # Get selected category + category = form.cleaned_data['category'] + + for template in category.get_parameter_templates(): + parameter = PartParameter.create(part=part, + template=template.parameter_template, + data=template.default_value, + save=True) + if parameter: + template_list.append(template.parameter_template) + + # Create part parameters for parent category + category_templates = form.cleaned_data['parent_category_templates'] + if category_templates: + # Get parent category category = form.cleaned_data['category'].get_root() for template in category.get_parameter_templates(): - PartParameter.create(part=part, - template=template.parameter_template, - data=template.default_value, - save=True) + # Check that template wasn't already added + if template.parameter_template not in template_list: + try: + PartParameter.create(part=part, + template=template.parameter_template, + data=template.default_value, + save=True) + except IntegrityError: + # PartParameter already exists + pass return self.renderJsonResponse(request, form, data, context=context) @@ -729,7 +753,8 @@ class PartCreate(AjaxCreateView): initials[label] = self.request.GET.get(label) # Automatically create part parameters from category templates - initials['category_templates'] = str2bool(InvenTreeSetting.get_setting('PART_CATEGORY_PARAMETERS', False)) + initials['selected_category_templates'] = str2bool(InvenTreeSetting.get_setting('PART_CATEGORY_PARAMETERS', False)) + initials['parent_category_templates'] = initials['selected_category_templates'] return initials @@ -2323,13 +2348,9 @@ class CategoryParameterTemplateCreate(AjaxCreateView): default_value = form.cleaned_data['default_value'] # Add parameter template and default value to all categories - for category_id, category_name in PartCategory.get_parent_categories(): - # Change category_id type to integer - category_id = int(category_id) + for category in PartCategory.objects.all(): # Skip selected category (will be processed in the post call) - if category_id != selected_category: - # Get category - category = PartCategory.objects.get(pk=category_id) + if category.pk != selected_category: try: cat_template = PartCategoryParameterTemplate.objects.create(category=category, parameter_template=parameter_template, @@ -2368,6 +2389,7 @@ class CategoryParameterTemplateEdit(AjaxUpdateView): form = super(AjaxUpdateView, self).get_form() form.fields['category'].widget = HiddenInput() + form.fields['add_to_all_categories'].widget = HiddenInput() if form.is_valid(): form.cleaned_data['category'] = self.kwargs.get('pk', None) From 279d5a00cedd84eedc37746df0e48095376e9eb4 Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 3 Nov 2020 16:58:53 -0500 Subject: [PATCH 14/52] Switched to get_ancestors to transverse all parents categories (not only root) --- InvenTree/part/views.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 857cf94106..25d2eb8bfb 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -714,20 +714,21 @@ class PartCreate(AjaxCreateView): # Create part parameters for parent category category_templates = form.cleaned_data['parent_category_templates'] if category_templates: - # Get parent category - category = form.cleaned_data['category'].get_root() + # Get parent categories + parent_categories = form.cleaned_data['category'].get_ancestors() - for template in category.get_parameter_templates(): - # Check that template wasn't already added - if template.parameter_template not in template_list: - try: - PartParameter.create(part=part, - template=template.parameter_template, - data=template.default_value, - save=True) - except IntegrityError: - # PartParameter already exists - pass + for category in parent_categories: + for template in category.get_parameter_templates(): + # Check that template wasn't already added + if template.parameter_template not in template_list: + try: + PartParameter.create(part=part, + template=template.parameter_template, + data=template.default_value, + save=True) + except IntegrityError: + # PartParameter already exists + pass return self.renderJsonResponse(request, form, data, context=context) From 4e157fe9561a8fa5171f5e73c451e03b695a514a Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 3 Nov 2020 17:05:08 -0500 Subject: [PATCH 15/52] Fixed text for parent categories checkbox --- InvenTree/part/forms.py | 2 +- InvenTree/part/test_param.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 47b7ac21bc..cb4691e05b 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -212,7 +212,7 @@ class EditPartForm(HelperForm): parent_category_templates = forms.BooleanField(required=False, initial=False, - label=_('Include parent category parameter templates'), + label=_('Include parent categories parameter templates'), widget=forms.HiddenInput()) class Meta: diff --git a/InvenTree/part/test_param.py b/InvenTree/part/test_param.py index ad0f5ed3b6..66b2bf303c 100644 --- a/InvenTree/part/test_param.py +++ b/InvenTree/part/test_param.py @@ -46,6 +46,7 @@ class TestParams(TestCase): t3.full_clean() t3.save() + # Category templates n = PartCategoryParameterTemplate.objects.all().count() self.assertEqual(n, 2) From 1c14c2237ac43e84e41d1f617dbf20cab1acbb50 Mon Sep 17 00:00:00 2001 From: eeintech Date: Wed, 4 Nov 2020 09:52:26 -0500 Subject: [PATCH 16/52] Moved category templates processing to Part save() method --- InvenTree/InvenTree/forms.py | 4 +- InvenTree/common/models.py | 6 +-- InvenTree/part/models.py | 42 +++++++++++++++++ InvenTree/part/views.py | 45 ++++--------------- .../InvenTree/settings/category.html | 5 +-- 5 files changed, 57 insertions(+), 45 deletions(-) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 3038a1cd7f..3a91821a19 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -219,10 +219,10 @@ class SettingCategorySelectForm(forms.ModelForm): Div( Div(Field('category'), css_class='col-sm-6', - style='width: 30%;'), + style='width: 70%;'), Div(StrictButton(_('Select Category'), css_class='btn btn-primary', type='submit'), css_class='col-sm-6', - style='width: auto; padding-left: 0;'), + style='width: 30%; padding-left: 0;'), css_class='row', ), ) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 9b52a89eba..e5bc10c906 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -86,9 +86,9 @@ class InvenTreeSetting(models.Model): }, 'PART_CATEGORY_PARAMETERS': { - 'name': _('Create Parameters From Category Templates'), - 'description': _('Automatically create part parameters from category templates'), - 'default': False, + 'name': _('Copy Category Parameter Templates'), + 'description': _('Copy category parameter templates when creating a part'), + 'default': True, 'validator': bool }, diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 705e4c6afd..633915466f 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -12,6 +12,7 @@ from django.core.exceptions import ValidationError from django.urls import reverse from django.db import models, transaction +from django.db.utils import IntegrityError from django.db.models import Sum, UniqueConstraint from django.db.models.functions import Coalesce from django.core.validators import MinValueValidator @@ -324,6 +325,9 @@ class Part(MPTTModel): If not, it is considered "orphaned" and will be deleted. """ + # Get category templates settings + add_category_templates = kwargs.pop('add_category_templates', None) + if self.pk: previous = Part.objects.get(pk=self.pk) @@ -339,6 +343,44 @@ class Part(MPTTModel): super().save(*args, **kwargs) + if add_category_templates: + # Get part category + category = self.category + + if add_category_templates: + # Store templates added to part + template_list = [] + + # Create part parameters for selected category + category_templates = add_category_templates['main'] + if category_templates: + for template in category.get_parameter_templates(): + parameter = PartParameter.create(part=self, + template=template.parameter_template, + data=template.default_value, + save=True) + if parameter: + template_list.append(template.parameter_template) + + # Create part parameters for parent category + category_templates = add_category_templates['parent'] + if category_templates: + # Get parent categories + parent_categories = category.get_ancestors() + + for category in parent_categories: + for template in category.get_parameter_templates(): + # Check that template wasn't already added + if template.parameter_template not in template_list: + try: + PartParameter.create(part=self, + template=template.parameter_template, + data=template.default_value, + save=True) + except IntegrityError: + # PartParameter already exists + pass + def __str__(self): return f"{self.full_name} - {self.description}" diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 25d2eb8bfb..65b74ec7b2 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -684,7 +684,14 @@ class PartCreate(AjaxCreateView): # Record the user who created this part part.creation_user = request.user - part.save() + # Store category templates settings + add_category_templates = { + 'main': form.cleaned_data['selected_category_templates'], + 'parent': form.cleaned_data['parent_category_templates'], + } + + # Save part and pass category template settings + part.save(**{'add_category_templates': add_category_templates}) data['pk'] = part.pk data['text'] = str(part) @@ -694,42 +701,6 @@ class PartCreate(AjaxCreateView): except AttributeError: pass - # Store templates added to part - template_list = [] - - # Create part parameters for selected category - category_templates = form.cleaned_data['selected_category_templates'] - if category_templates: - # Get selected category - category = form.cleaned_data['category'] - - for template in category.get_parameter_templates(): - parameter = PartParameter.create(part=part, - template=template.parameter_template, - data=template.default_value, - save=True) - if parameter: - template_list.append(template.parameter_template) - - # Create part parameters for parent category - category_templates = form.cleaned_data['parent_category_templates'] - if category_templates: - # Get parent categories - parent_categories = form.cleaned_data['category'].get_ancestors() - - for category in parent_categories: - for template in category.get_parameter_templates(): - # Check that template wasn't already added - if template.parameter_template not in template_list: - try: - PartParameter.create(part=part, - template=template.parameter_template, - data=template.default_value, - save=True) - except IntegrityError: - # PartParameter already exists - pass - return self.renderJsonResponse(request, form, data, context=context) def get_initial(self): diff --git a/InvenTree/templates/InvenTree/settings/category.html b/InvenTree/templates/InvenTree/settings/category.html index d17efd9c9a..c84573e8b4 100644 --- a/InvenTree/templates/InvenTree/settings/category.html +++ b/InvenTree/templates/InvenTree/settings/category.html @@ -37,7 +37,8 @@ {% block js_ready %} {{ block.super }} - + + {# Convert dropdown to select2 format #} $(document).ready(function() { attachSelect('#category-select'); }); @@ -78,7 +79,6 @@ ] }); - $("#new-param").click(function() { launchModalForm("{% url 'category-param-template-create' category.pk %}", { success: function() { @@ -110,6 +110,5 @@ } }); }); - {% endif %} {% endblock %} From e401bb8e3c5e27de81a33d61de0e95e2d9bc9971 Mon Sep 17 00:00:00 2001 From: eeintech Date: Wed, 4 Nov 2020 12:06:07 -0500 Subject: [PATCH 17/52] Improved tests, fixed admin, improved naming --- InvenTree/part/admin.py | 9 +------ InvenTree/part/fixtures/params.yaml | 2 +- InvenTree/part/forms.py | 2 +- InvenTree/part/models.py | 5 +++- InvenTree/part/test_param.py | 39 ++++++++++++++++++++++++++--- 5 files changed, 42 insertions(+), 15 deletions(-) diff --git a/InvenTree/part/admin.py b/InvenTree/part/admin.py index 02e6974aef..1d1c981b74 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -277,14 +277,7 @@ class ParameterAdmin(ImportExportModelAdmin): class PartCategoryParameterAdmin(admin.ModelAdmin): - def get_form(self, request, obj=None, **kwargs): - """ Display only parent categories as choices for category field """ - - form = super().get_form(request, obj, **kwargs) - - form.base_fields['category'].choices = PartCategory.get_parent_categories() - - return form + pass class PartSellPriceBreakAdmin(admin.ModelAdmin): diff --git a/InvenTree/part/fixtures/params.yaml b/InvenTree/part/fixtures/params.yaml index 2dfcada698..e65c7335cc 100644 --- a/InvenTree/part/fixtures/params.yaml +++ b/InvenTree/part/fixtures/params.yaml @@ -42,7 +42,7 @@ default_value: '2.8' - model: part.PartCategoryParameterTemplate - pk: 3 + pk: 2 fields: category: 7 parameter_template: 3 diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index cb4691e05b..20507bd392 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -207,7 +207,7 @@ class EditPartForm(HelperForm): selected_category_templates = forms.BooleanField(required=False, initial=False, - label=_('Include selected category parameter templates'), + label=_('Include category parameter templates'), widget=forms.HiddenInput()) parent_category_templates = forms.BooleanField(required=False, diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 633915466f..6ebc6fe804 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -180,7 +180,9 @@ class PartCategory(InvenTreeTree): def get_parameter_templates(self): """ Return parameter templates associated to category """ - return PartCategoryParameterTemplate.objects.filter(category=self.id) + prefetch = PartCategoryParameterTemplate.objects.prefetch_related('category', 'parameter_template') + + return prefetch.filter(category=self.id) @receiver(pre_delete, sender=PartCategory, dispatch_uid='partcategory_delete_log') @@ -1701,6 +1703,7 @@ class PartCategoryParameterTemplate(models.Model): def __str__(self): """ String representation of a PartCategoryParameterTemplate (admin interface) """ + if self.default_value: return f'{self.category.name} | {self.parameter_template.name} | {self.default_value}' else: diff --git a/InvenTree/part/test_param.py b/InvenTree/part/test_param.py index 66b2bf303c..24eee44d89 100644 --- a/InvenTree/part/test_param.py +++ b/InvenTree/part/test_param.py @@ -3,10 +3,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.test import TestCase +from django.test import TestCase, TransactionTestCase import django.core.exceptions as django_exceptions -from .models import PartCategory +from .models import Part, PartCategory from .models import PartParameter, PartParameterTemplate from .models import PartCategoryParameterTemplate @@ -46,15 +46,46 @@ class TestParams(TestCase): t3.full_clean() t3.save() + +class TestCategoryTemplates(TransactionTestCase): + + fixtures = [ + 'location', + 'category', + 'part', + 'params' + ] + + def test_validate(self): + # Category templates n = PartCategoryParameterTemplate.objects.all().count() self.assertEqual(n, 2) - category = PartCategory.objects.get(pk=7) + category = PartCategory.objects.get(pk=8) + t1 = PartParameterTemplate.objects.get(pk=2) c1 = PartCategoryParameterTemplate(category=category, parameter_template=t1, default_value='xyz') c1.save() - self.assertEqual(n + 1, PartCategoryParameterTemplate.objects.filter(category=7).count()) + n = PartCategoryParameterTemplate.objects.all().count() + self.assertEqual(n, 3) + + # Get test part + part = Part.objects.get(pk=1) + + # Get part parameters count + n_param = part.get_parameters().count() + + add_category_templates = { + 'main': True, + 'parent': True, + } + # Save it with category parameters + part.save(**{'add_category_templates': add_category_templates}) + + # Check new part parameters count + # Only 2 parameters should be added as one already existed with same template + self.assertEqual(n_param + 2, part.get_parameters().count()) From 324645b67c58c81d8288a3fefbb9efa7eb0b6150 Mon Sep 17 00:00:00 2001 From: eeintech Date: Wed, 4 Nov 2020 12:26:10 -0500 Subject: [PATCH 18/52] Added same level category checkbox and method when adding category template --- InvenTree/part/forms.py | 7 ++++++- InvenTree/part/views.py | 28 ++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 20507bd392..63d884a15e 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -282,16 +282,21 @@ class EditCategoryForm(HelperForm): class EditCategoryParameterTemplateForm(HelperForm): """ Form for editing a PartCategoryParameterTemplate object """ + add_to_same_level_categories = forms.BooleanField(required=False, + initial=False, + help_text=_('Add parameter template to same level categories')) + add_to_all_categories = forms.BooleanField(required=False, initial=False, help_text=_('Add parameter template to all categories')) - + class Meta: model = PartCategoryParameterTemplate fields = [ 'category', 'parameter_template', 'default_value', + 'add_to_same_level_categories', 'add_to_all_categories', ] diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 65b74ec7b2..b522a66159 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -2305,6 +2305,8 @@ class CategoryParameterTemplateCreate(AjaxCreateView): - If the add_to_all_categories object is set, link parameter template to all categories + - If the add_to_same_level_categories object is set, link parameter template to + same level categories """ form = self.get_form() @@ -2312,17 +2314,26 @@ class CategoryParameterTemplateCreate(AjaxCreateView): valid = form.is_valid() if valid: - all_categories = form.cleaned_data['add_to_all_categories'] + add_to_same_level_categories = form.cleaned_data['add_to_same_level_categories'] + add_to_all_categories = form.cleaned_data['add_to_all_categories'] - if all_categories: - selected_category = int(self.kwargs.get('pk', 0)) - parameter_template = form.cleaned_data['parameter_template'] - default_value = form.cleaned_data['default_value'] + selected_category = PartCategory.objects.get(pk=int(self.kwargs['pk'])) + parameter_template = form.cleaned_data['parameter_template'] + default_value = form.cleaned_data['default_value'] - # Add parameter template and default value to all categories - for category in PartCategory.objects.all(): + categories = PartCategory.objects.all() + + if add_to_same_level_categories and not add_to_all_categories: + # Get level + level = selected_category.level + # Filter same level categories + categories = categories.filter(level=level) + + if add_to_same_level_categories or add_to_all_categories: + # Add parameter template and default value to categories + for category in categories: # Skip selected category (will be processed in the post call) - if category.pk != selected_category: + if category.pk != selected_category.pk: try: cat_template = PartCategoryParameterTemplate.objects.create(category=category, parameter_template=parameter_template, @@ -2362,6 +2373,7 @@ class CategoryParameterTemplateEdit(AjaxUpdateView): form.fields['category'].widget = HiddenInput() form.fields['add_to_all_categories'].widget = HiddenInput() + form.fields['add_to_same_level_categories'].widget = HiddenInput() if form.is_valid(): form.cleaned_data['category'] = self.kwargs.get('pk', None) From 734436b02ef84267911da39eeda4474eb2ff3fa4 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 10 Nov 2020 16:22:42 +1100 Subject: [PATCH 19/52] Add integration of django-money - Proper currency support - Add PurchasePrice field to StockItem model - This keeps track of both the price and the currency - Display purchase price on the stockitem detail page --- InvenTree/InvenTree/settings.py | 1 + InvenTree/stock/forms.py | 2 ++ .../migrations/0053_auto_20201110_0513.py | 24 +++++++++++++++++++ InvenTree/stock/models.py | 12 ++++++++++ .../stock/templates/stock/item_base.html | 7 ++++++ requirements.txt | 1 + 6 files changed, 47 insertions(+) create mode 100644 InvenTree/stock/migrations/0053_auto_20201110_0513.py diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 208220e23a..e1f479fff7 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -155,6 +155,7 @@ INSTALLED_APPS = [ 'markdownify', # Markdown template rendering 'django_tex', # LaTeX output 'django_admin_shell', # Python shell for the admin interface + 'djmoney', # django-money integration ] LOGGING = { diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index d9937f3106..77d0f58295 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -124,6 +124,7 @@ class CreateStockItemForm(HelperForm): fields = [ 'part', 'supplier_part', + 'purchase_price', 'location', 'quantity', 'batch', @@ -399,6 +400,7 @@ class EditStockItemForm(HelperForm): 'serial', 'batch', 'status', + 'purchase_price', 'link', 'delete_on_deplete', ] diff --git a/InvenTree/stock/migrations/0053_auto_20201110_0513.py b/InvenTree/stock/migrations/0053_auto_20201110_0513.py new file mode 100644 index 0000000000..db002c9135 --- /dev/null +++ b/InvenTree/stock/migrations/0053_auto_20201110_0513.py @@ -0,0 +1,24 @@ +# Generated by Django 3.0.7 on 2020-11-10 05:13 + +from django.db import migrations +import djmoney.models.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0052_stockitem_is_building'), + ] + + operations = [ + migrations.AddField( + model_name='stockitem', + name='purchase_price', + field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency='USD', help_text='Single unit purchase price at time of purchase', max_digits=19, null=True, verbose_name='Purchase Price'), + ), + migrations.AddField( + model_name='stockitem', + name='purchase_price_currency', + field=djmoney.models.fields.CurrencyField(choices=[('XUA', 'ADB Unit of Account'), ('AFN', 'Afghani'), ('DZD', 'Algerian Dinar'), ('ARS', 'Argentine Peso'), ('AMD', 'Armenian Dram'), ('AWG', 'Aruban Guilder'), ('AUD', 'Australian Dollar'), ('AZN', 'Azerbaijanian Manat'), ('BSD', 'Bahamian Dollar'), ('BHD', 'Bahraini Dinar'), ('THB', 'Baht'), ('PAB', 'Balboa'), ('BBD', 'Barbados Dollar'), ('BYN', 'Belarussian Ruble'), ('BYR', 'Belarussian Ruble'), ('BZD', 'Belize Dollar'), ('BMD', 'Bermudian Dollar (customarily known as Bermuda Dollar)'), ('BTN', 'Bhutanese ngultrum'), ('VEF', 'Bolivar Fuerte'), ('BOB', 'Boliviano'), ('XBA', 'Bond Markets Units European Composite Unit (EURCO)'), ('BRL', 'Brazilian Real'), ('BND', 'Brunei Dollar'), ('BGN', 'Bulgarian Lev'), ('BIF', 'Burundi Franc'), ('XOF', 'CFA Franc BCEAO'), ('XAF', 'CFA franc BEAC'), ('XPF', 'CFP Franc'), ('CAD', 'Canadian Dollar'), ('CVE', 'Cape Verde Escudo'), ('KYD', 'Cayman Islands Dollar'), ('CLP', 'Chilean peso'), ('XTS', 'Codes specifically reserved for testing purposes'), ('COP', 'Colombian peso'), ('KMF', 'Comoro Franc'), ('CDF', 'Congolese franc'), ('BAM', 'Convertible Marks'), ('NIO', 'Cordoba Oro'), ('CRC', 'Costa Rican Colon'), ('HRK', 'Croatian Kuna'), ('CUP', 'Cuban Peso'), ('CUC', 'Cuban convertible peso'), ('CZK', 'Czech Koruna'), ('GMD', 'Dalasi'), ('DKK', 'Danish Krone'), ('MKD', 'Denar'), ('DJF', 'Djibouti Franc'), ('STD', 'Dobra'), ('DOP', 'Dominican Peso'), ('VND', 'Dong'), ('XCD', 'East Caribbean Dollar'), ('EGP', 'Egyptian Pound'), ('SVC', 'El Salvador Colon'), ('ETB', 'Ethiopian Birr'), ('EUR', 'Euro'), ('XBB', 'European Monetary Unit (E.M.U.-6)'), ('XBD', 'European Unit of Account 17(E.U.A.-17)'), ('XBC', 'European Unit of Account 9(E.U.A.-9)'), ('FKP', 'Falkland Islands Pound'), ('FJD', 'Fiji Dollar'), ('HUF', 'Forint'), ('GHS', 'Ghana Cedi'), ('GIP', 'Gibraltar Pound'), ('XAU', 'Gold'), ('XFO', 'Gold-Franc'), ('PYG', 'Guarani'), ('GNF', 'Guinea Franc'), ('GYD', 'Guyana Dollar'), ('HTG', 'Haitian gourde'), ('HKD', 'Hong Kong Dollar'), ('UAH', 'Hryvnia'), ('ISK', 'Iceland Krona'), ('INR', 'Indian Rupee'), ('IRR', 'Iranian Rial'), ('IQD', 'Iraqi Dinar'), ('IMP', 'Isle of Man Pound'), ('JMD', 'Jamaican Dollar'), ('JOD', 'Jordanian Dinar'), ('KES', 'Kenyan Shilling'), ('PGK', 'Kina'), ('LAK', 'Kip'), ('KWD', 'Kuwaiti Dinar'), ('AOA', 'Kwanza'), ('MMK', 'Kyat'), ('GEL', 'Lari'), ('LVL', 'Latvian Lats'), ('LBP', 'Lebanese Pound'), ('ALL', 'Lek'), ('HNL', 'Lempira'), ('SLL', 'Leone'), ('LSL', 'Lesotho loti'), ('LRD', 'Liberian Dollar'), ('LYD', 'Libyan Dinar'), ('SZL', 'Lilangeni'), ('LTL', 'Lithuanian Litas'), ('MGA', 'Malagasy Ariary'), ('MWK', 'Malawian Kwacha'), ('MYR', 'Malaysian Ringgit'), ('TMM', 'Manat'), ('MUR', 'Mauritius Rupee'), ('MZN', 'Metical'), ('MXV', 'Mexican Unidad de Inversion (UDI)'), ('MXN', 'Mexican peso'), ('MDL', 'Moldovan Leu'), ('MAD', 'Moroccan Dirham'), ('BOV', 'Mvdol'), ('NGN', 'Naira'), ('ERN', 'Nakfa'), ('NAD', 'Namibian Dollar'), ('NPR', 'Nepalese Rupee'), ('ANG', 'Netherlands Antillian Guilder'), ('ILS', 'New Israeli Sheqel'), ('RON', 'New Leu'), ('TWD', 'New Taiwan Dollar'), ('NZD', 'New Zealand Dollar'), ('KPW', 'North Korean Won'), ('NOK', 'Norwegian Krone'), ('PEN', 'Nuevo Sol'), ('MRO', 'Ouguiya'), ('TOP', 'Paanga'), ('PKR', 'Pakistan Rupee'), ('XPD', 'Palladium'), ('MOP', 'Pataca'), ('PHP', 'Philippine Peso'), ('XPT', 'Platinum'), ('GBP', 'Pound Sterling'), ('BWP', 'Pula'), ('QAR', 'Qatari Rial'), ('GTQ', 'Quetzal'), ('ZAR', 'Rand'), ('OMR', 'Rial Omani'), ('KHR', 'Riel'), ('MVR', 'Rufiyaa'), ('IDR', 'Rupiah'), ('RUB', 'Russian Ruble'), ('RWF', 'Rwanda Franc'), ('XDR', 'SDR'), ('SHP', 'Saint Helena Pound'), ('SAR', 'Saudi Riyal'), ('RSD', 'Serbian Dinar'), ('SCR', 'Seychelles Rupee'), ('XAG', 'Silver'), ('SGD', 'Singapore Dollar'), ('SBD', 'Solomon Islands Dollar'), ('KGS', 'Som'), ('SOS', 'Somali Shilling'), ('TJS', 'Somoni'), ('SSP', 'South Sudanese Pound'), ('LKR', 'Sri Lanka Rupee'), ('XSU', 'Sucre'), ('SDG', 'Sudanese Pound'), ('SRD', 'Surinam Dollar'), ('SEK', 'Swedish Krona'), ('CHF', 'Swiss Franc'), ('SYP', 'Syrian Pound'), ('BDT', 'Taka'), ('WST', 'Tala'), ('TZS', 'Tanzanian Shilling'), ('KZT', 'Tenge'), ('XXX', 'The codes assigned for transactions where no currency is involved'), ('TTD', 'Trinidad and Tobago Dollar'), ('MNT', 'Tugrik'), ('TND', 'Tunisian Dinar'), ('TRY', 'Turkish Lira'), ('TMT', 'Turkmenistan New Manat'), ('TVD', 'Tuvalu dollar'), ('AED', 'UAE Dirham'), ('XFU', 'UIC-Franc'), ('USD', 'US Dollar'), ('USN', 'US Dollar (Next day)'), ('UGX', 'Uganda Shilling'), ('CLF', 'Unidad de Fomento'), ('COU', 'Unidad de Valor Real'), ('UYI', 'Uruguay Peso en Unidades Indexadas (URUIURUI)'), ('UYU', 'Uruguayan peso'), ('UZS', 'Uzbekistan Sum'), ('VUV', 'Vatu'), ('CHE', 'WIR Euro'), ('CHW', 'WIR Franc'), ('KRW', 'Won'), ('YER', 'Yemeni Rial'), ('JPY', 'Yen'), ('CNY', 'Yuan Renminbi'), ('ZMK', 'Zambian Kwacha'), ('ZMW', 'Zambian Kwacha'), ('ZWD', 'Zimbabwe Dollar A/06'), ('ZWN', 'Zimbabwe dollar A/08'), ('ZWL', 'Zimbabwe dollar A/09'), ('PLN', 'Zloty')], default='USD', editable=False, max_length=3), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 4899ddee8d..4f4057eb88 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -24,6 +24,8 @@ from markdownx.models import MarkdownxField from mptt.models import MPTTModel, TreeForeignKey +from djmoney.models.fields import MoneyField + from decimal import Decimal, InvalidOperation from datetime import datetime from InvenTree import helpers @@ -135,6 +137,7 @@ class StockItem(MPTTModel): infinite: If True this StockItem can never be exhausted sales_order: Link to a SalesOrder object (if the StockItem has been assigned to a SalesOrder) build_order: Link to a BuildOrder object (if the StockItem has been assigned to a BuildOrder) + purchase_price: The unit purchase price for this StockItem - this is the unit price at time of purchase (if this item was purchased from an external supplier) """ # A Query filter which will be re-used in multiple places to determine if a StockItem is actually "in stock" @@ -456,6 +459,15 @@ class StockItem(MPTTModel): help_text=_('Stock Item Notes') ) + purchase_price = MoneyField( + max_digits=19, + decimal_places=4, + default_currency='USD', + null=True, + verbose_name=_('Purchase Price'), + help_text=_('Single unit purchase price at time of purchase'), + ) + def clearAllocations(self): """ Clear all order allocations for this StockItem: diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 71bdc3592d..38ed9723a6 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -266,6 +266,13 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {{ item.purchase_order }} {% endif %} + {% if item.purchase_price %} + + + {% trans "Purchase Price" %} + {{ item.purchase_price }} + + {% endif %} {% if item.parent %} diff --git a/requirements.txt b/requirements.txt index 5d2917b57d..bc5fff38cc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,5 +26,6 @@ django-tex==1.1.7 # LaTeX PDF export django-weasyprint==1.0.1 # HTML PDF export django-debug-toolbar==2.2 # Debug / profiling toolbar django-admin-shell==0.1.2 # Python shell for the admin interface +django-money==1.1 # Django app for currency management inventree # Install the latest version of the InvenTree API python library \ No newline at end of file From 48c20c600a144eb38a782385dfc0a6f501ed2c79 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 10 Nov 2020 16:28:55 +1100 Subject: [PATCH 20/52] List supported currencies in the configuration template --- InvenTree/InvenTree/settings.py | 8 ++++++++ InvenTree/config_template.yaml | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index e1f479fff7..9c58686b56 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -352,6 +352,14 @@ LANGUAGES = [ ('pk', _('Polish')), ] +# Currencies available for use +CURRENCIES = CONFIG.get( + 'currencies', + [ + 'AUD', 'CAD', 'EUR', 'GBP', 'JPY', 'NZD', 'USD', + ], +) + LOCALE_PATHS = ( os.path.join(BASE_DIR, 'locale/'), ) diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml index ecd0a241cf..310fedd70e 100644 --- a/InvenTree/config_template.yaml +++ b/InvenTree/config_template.yaml @@ -26,6 +26,17 @@ language: en-us # Select an option from the "TZ database name" column timezone: UTC +# List of currencies supported by default. +# Add other currencies here to allow use in InvenTree +currencies: + - AUD + - CAD + - EUR + - GBP + - JPY + - NZD + - USD + # Set debug to False to run in production mode debug: True From 978fd7c683517431d4eeac6878acdbad682b8307 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 10 Nov 2020 17:08:08 +1100 Subject: [PATCH 21/52] Implement default currency selection - Add 'choices' option to InvenTreeSetting class - Add support for ChoiceField in InvenTreeSetting form --- InvenTree/common/models.py | 39 +++++++++++++++++++ InvenTree/common/views.py | 8 +++- .../templates/InvenTree/settings/global.html | 1 + 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 8254d161d6..6844fa2ab7 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -12,6 +12,8 @@ import decimal from django.db import models from django.conf import settings +import djmoney.settings + from django.utils.translation import ugettext as _ from django.core.validators import MinValueValidator, MaxValueValidator from django.core.exceptions import ValidationError @@ -59,6 +61,13 @@ class InvenTreeSetting(models.Model): 'default': 'My company name', }, + 'INVENTREE_DEFAULT_CURRENCY': { + 'name': _('Default Currency'), + 'description': _('Default currency'), + 'default': 'USD', + 'choices': djmoney.settings.CURRENCY_CHOICES, + }, + 'PART_IPN_REGEX': { 'name': _('IPN Regex'), 'description': _('Regular expression pattern for matching Part IPN') @@ -226,6 +235,29 @@ class InvenTreeSetting(models.Model): else: return '' + @classmethod + def get_setting_choices(cls, key): + """ + Return the validator choices available for a particular setting. + """ + + key = str(key).strip().upper() + + if key in cls.GLOBAL_SETTINGS: + setting = cls.GLOBAL_SETTINGS[key] + choices = setting.get('choices', None) + else: + choices = None + + """ + TODO: + if type(choices) is function: + # Evaluate the function (we expect it will return a list of tuples...) + return choices() + """ + + return choices + @classmethod def get_setting_object(cls, key): """ @@ -396,6 +428,13 @@ class InvenTreeSetting(models.Model): except InvenTreeSetting.DoesNotExist: pass + def choices(self): + """ + Return the available choices for this setting (or None if no choices are defined) + """ + + return InvenTreeSetting.get_setting_choices(self.key) + def is_bool(self): """ Check if this setting is required to be a boolean value diff --git a/InvenTree/common/views.py b/InvenTree/common/views.py index cfcee8bfa9..ff72a44e3d 100644 --- a/InvenTree/common/views.py +++ b/InvenTree/common/views.py @@ -6,7 +6,7 @@ Django views for interacting with common models from __future__ import unicode_literals from django.utils.translation import ugettext as _ -from django.forms import CheckboxInput +from django.forms import CheckboxInput, Select from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView from InvenTree.helpers import str2bool @@ -75,7 +75,11 @@ class SettingEdit(AjaxUpdateView): setting = self.get_object() - if setting.is_bool(): + choices = setting.choices() + + if choices is not None: + form.fields['value'].widget = Select(choices=choices) + elif setting.is_bool(): form.fields['value'].widget = CheckboxInput() self.object.value = str2bool(setting.value) diff --git a/InvenTree/templates/InvenTree/settings/global.html b/InvenTree/templates/InvenTree/settings/global.html index 7dcdd54bea..775d30b915 100644 --- a/InvenTree/templates/InvenTree/settings/global.html +++ b/InvenTree/templates/InvenTree/settings/global.html @@ -17,6 +17,7 @@ {% include "InvenTree/settings/setting.html" with key="INVENTREE_INSTANCE" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_COMPANY_NAME" %} + {% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" %} From e4f2eecb3bbefc3d6d3fa1fa72824339e80cb157 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 10 Nov 2020 20:12:39 +1100 Subject: [PATCH 22/52] Remove defunct 'build_order' field from StockItem model - This is now handled by the new-and-improved build system, no longer required --- .../migrations/0023_auto_20201110_0911.py | 20 +++++++++++++++++++ InvenTree/build/models.py | 1 - .../migrations/0037_auto_20201110_0911.py | 20 +++++++++++++++++++ InvenTree/order/models.py | 1 - InvenTree/stock/admin.py | 2 -- InvenTree/stock/api.py | 6 +----- .../0054_remove_stockitem_build_order.py | 17 ++++++++++++++++ InvenTree/stock/models.py | 17 ---------------- InvenTree/stock/serializers.py | 2 -- .../stock/templates/stock/item_base.html | 6 ------ 10 files changed, 58 insertions(+), 34 deletions(-) create mode 100644 InvenTree/build/migrations/0023_auto_20201110_0911.py create mode 100644 InvenTree/order/migrations/0037_auto_20201110_0911.py create mode 100644 InvenTree/stock/migrations/0054_remove_stockitem_build_order.py diff --git a/InvenTree/build/migrations/0023_auto_20201110_0911.py b/InvenTree/build/migrations/0023_auto_20201110_0911.py new file mode 100644 index 0000000000..3d95e6844e --- /dev/null +++ b/InvenTree/build/migrations/0023_auto_20201110_0911.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.7 on 2020-11-10 09:11 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0054_remove_stockitem_build_order'), + ('build', '0022_buildorderattachment'), + ] + + operations = [ + migrations.AlterField( + model_name='builditem', + name='stock_item', + field=models.ForeignKey(help_text='Source stock item', limit_choices_to={'belongs_to': None, 'sales_order': None}, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='stock.StockItem'), + ), + ] diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index a18328be1a..a18471ccc6 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -962,7 +962,6 @@ class BuildItem(models.Model): related_name='allocations', help_text=_('Source stock item'), limit_choices_to={ - 'build_order': None, 'sales_order': None, 'belongs_to': None, } diff --git a/InvenTree/order/migrations/0037_auto_20201110_0911.py b/InvenTree/order/migrations/0037_auto_20201110_0911.py new file mode 100644 index 0000000000..9594424e5d --- /dev/null +++ b/InvenTree/order/migrations/0037_auto_20201110_0911.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.7 on 2020-11-10 09:11 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0054_remove_stockitem_build_order'), + ('order', '0036_auto_20200831_0912'), + ] + + operations = [ + migrations.AlterField( + model_name='salesorderallocation', + name='item', + field=models.ForeignKey(help_text='Select stock item to allocate', limit_choices_to={'belongs_to': None, 'part__salable': True, 'sales_order': None}, on_delete=django.db.models.deletion.CASCADE, related_name='sales_order_allocations', to='stock.StockItem'), + ), + ] diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index b193454e1f..359b0cf752 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -621,7 +621,6 @@ class SalesOrderAllocation(models.Model): 'part__salable': True, 'belongs_to': None, 'sales_order': None, - 'build_order': None, }, help_text=_('Select stock item to allocate') ) diff --git a/InvenTree/stock/admin.py b/InvenTree/stock/admin.py index 5917ed8e22..5f3c08839d 100644 --- a/InvenTree/stock/admin.py +++ b/InvenTree/stock/admin.py @@ -83,8 +83,6 @@ class StockItemResource(ModelResource): sales_order = Field(attribute='sales_order', widget=widgets.ForeignKeyWidget(SalesOrder)) - build_order = Field(attribute='build_order', widget=widgets.ForeignKeyWidget(Build)) - purchase_order = Field(attribute='purchase_order', widget=widgets.ForeignKeyWidget(PurchaseOrder)) # Date management diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 5d71d00a0c..dc16fb52a6 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -488,11 +488,7 @@ class StockList(generics.ListCreateAPIView): if build: queryset = queryset.filter(build=build) - build_order = params.get('build_order', None) - - if build_order: - queryset = queryset.filter(build_order=build_order) - + # Filter by 'is building' status is_building = params.get('is_building', None) if is_building: diff --git a/InvenTree/stock/migrations/0054_remove_stockitem_build_order.py b/InvenTree/stock/migrations/0054_remove_stockitem_build_order.py new file mode 100644 index 0000000000..b0da00ab52 --- /dev/null +++ b/InvenTree/stock/migrations/0054_remove_stockitem_build_order.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.7 on 2020-11-10 09:11 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0053_auto_20201110_0513'), + ] + + operations = [ + migrations.RemoveField( + model_name='stockitem', + name='build_order', + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 4f4057eb88..9e31774946 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -136,7 +136,6 @@ class StockItem(MPTTModel): purchase_order: Link to a PurchaseOrder (if this stock item was created from a PurchaseOrder) infinite: If True this StockItem can never be exhausted sales_order: Link to a SalesOrder object (if the StockItem has been assigned to a SalesOrder) - build_order: Link to a BuildOrder object (if the StockItem has been assigned to a BuildOrder) purchase_price: The unit purchase price for this StockItem - this is the unit price at time of purchase (if this item was purchased from an external supplier) """ @@ -144,7 +143,6 @@ class StockItem(MPTTModel): IN_STOCK_FILTER = Q( quantity__gt=0, sales_order=None, - build_order=None, belongs_to=None, customer=None, is_building=False, @@ -430,14 +428,6 @@ class StockItem(MPTTModel): related_name='stock_items', null=True, blank=True) - build_order = models.ForeignKey( - 'build.Build', - on_delete=models.SET_NULL, - verbose_name=_("Destination Build Order"), - related_name='stock_items', - null=True, blank=True - ) - # last time the stock was checked / counted stocktake_date = models.DateField(blank=True, null=True) @@ -614,9 +604,6 @@ class StockItem(MPTTModel): if self.sales_order is not None: return False - if self.build_order is not None: - return False - return True def installedItemCount(self): @@ -750,10 +737,6 @@ class StockItem(MPTTModel): if self.sales_order is not None: return False - # Not 'in stock' if it has been allocated to a BuildOrder - if self.build_order is not None: - return False - # Not 'in stock' if it has been assigned to a customer if self.customer is not None: return False diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 5bea1c4aae..e8675a8fff 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -73,7 +73,6 @@ class StockItemSerializer(InvenTreeModelSerializer): return queryset.prefetch_related( 'belongs_to', 'build', - 'build_order', 'customer', 'sales_order', 'supplier_part', @@ -155,7 +154,6 @@ class StockItemSerializer(InvenTreeModelSerializer): 'batch', 'belongs_to', 'build', - 'build_order', 'customer', 'in_stock', 'is_building', diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 38ed9723a6..5e9d26c5cf 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -221,12 +221,6 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% trans "Sales Order" %} {{ item.sales_order.reference }} - {{ item.sales_order.customer.name }} - {% elif item.build_order %} - - - {% trans "Build Order" %} - {{ item.build_order }} - {% else %} From 83582ae87fa7235e10af6c84659fef2477b96f8b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 10 Nov 2020 22:25:05 +1100 Subject: [PATCH 23/52] Add custom migration for the part_supplierpricebreak model - Copies across existing pricing data - Yikes --- InvenTree/common/models.py | 10 ++ .../migrations/0025_auto_20201110_1001.py | 24 +++ .../migrations/0026_auto_20201110_1011.py | 141 ++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 InvenTree/company/migrations/0025_auto_20201110_1001.py create mode 100644 InvenTree/company/migrations/0026_auto_20201110_1011.py diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 6844fa2ab7..966ed48f2f 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -13,6 +13,7 @@ from django.db import models from django.conf import settings import djmoney.settings +from djmoney.models.fields import MoneyField from django.utils.translation import ugettext as _ from django.core.validators import MinValueValidator, MaxValueValidator @@ -536,6 +537,15 @@ class PriceBreak(models.Model): currency = models.ForeignKey(Currency, blank=True, null=True, on_delete=models.SET_NULL) + price = MoneyField( + max_digits=19, + decimal_places=4, + default_currency='USD', + null=True, + verbose_name=_('Price'), + help_text=_('Unit price at specified quantity'), + ) + @property def symbol(self): return self.currency.symbol if self.currency else '' diff --git a/InvenTree/company/migrations/0025_auto_20201110_1001.py b/InvenTree/company/migrations/0025_auto_20201110_1001.py new file mode 100644 index 0000000000..0fd62c0a23 --- /dev/null +++ b/InvenTree/company/migrations/0025_auto_20201110_1001.py @@ -0,0 +1,24 @@ +# Generated by Django 3.0.7 on 2020-11-10 10:01 + +from django.db import migrations, connection +import djmoney.models.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0024_unique_name_email_constraint'), + ] + + operations = [ + migrations.AddField( + model_name='supplierpricebreak', + name='price', + field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency='USD', help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price'), + ), + migrations.AddField( + model_name='supplierpricebreak', + name='price_currency', + field=djmoney.models.fields.CurrencyField(choices=[('AUD', 'Australian Dollar'), ('CAD', 'Canadian Dollar'), ('EUR', 'Euro'), ('NZD', 'New Zealand Dollar'), ('GBP', 'Pound Sterling'), ('USD', 'US Dollar'), ('JPY', 'Yen')], default='USD', editable=False, max_length=3), + ), + ] diff --git a/InvenTree/company/migrations/0026_auto_20201110_1011.py b/InvenTree/company/migrations/0026_auto_20201110_1011.py new file mode 100644 index 0000000000..ffaf99842c --- /dev/null +++ b/InvenTree/company/migrations/0026_auto_20201110_1011.py @@ -0,0 +1,141 @@ +# Generated by Django 3.0.7 on 2020-11-10 10:11 + +import sys + +from moneyed import CURRENCIES +from django.db import migrations, connection +from company.models import SupplierPriceBreak + + +def migrate_currencies(apps, schema_editor): + """ + Migrate from the 'old' method of handling currencies, + to the new method which uses the django-money library. + + Previously, we created a custom Currency model, + which was very simplistic. + + Here we will attempt to map each existing "currency" reference + for the SupplierPriceBreak model, to a new django-money compatible currency. + """ + + print("Updating currency references for SupplierPriceBreak model...") + + # A list of available currency codes + currency_codes = CURRENCIES.keys() + + cursor = connection.cursor() + + # The 'suffix' field denotes the currency code + response = cursor.execute('SELECT id, suffix, description from common_currency;').fetchall() + + remap = {} + + for index, row in enumerate(response): + pk, suffix, description = row + + suffix = suffix.strip().upper() + + if suffix not in currency_codes: + print("Missing suffix:", suffix) + + while suffix not in currency_codes: + # Ask the user to input a valid currency + print(f"Could not find a valid currency matching '{suffix}'.") + print("Please enter a valid currency code") + suffix = str(input("> ")).strip() + + if pk not in remap.keys(): + remap[pk] = suffix + + # Now iterate through each SupplierPriceBreak and update the rows + response = cursor.execute('SELECT id, cost, currency_id, price, price_currency from part_supplierpricebreak;').fetchall() + + count = 0 + + for index, row in enumerate(response): + pk, cost, currency_id, price, price_currency = row + + # Copy the 'cost' field across to the 'price' field + response = cursor.execute(f'UPDATE part_supplierpricebreak set price={cost} where id={pk};') + + # Extract the updated currency code + currency_code = remap.get(currency_id, 'USD') + + # Update the currency code + response = cursor.execute(f'UPDATE part_supplierpricebreak set price_currency= "{currency_code}" where id={pk};') + + count += 1 + + print(f"Updated {count} SupplierPriceBreak rows") + +def reverse_currencies(apps, schema_editor): + """ + Reverse the "update" process. + + Here we may be in the situation that the legacy "Currency" table is empty, + and so we have to re-populate it based on the new price_currency codes. + """ + + print("Reversing currency migration...") + + cursor = connection.cursor() + + # Extract a list of currency codes which are in use + response = cursor.execute(f'SELECT id, price, price_currency from part_supplierpricebreak;').fetchall() + + codes_in_use = set() + + for index, row in enumerate(response): + pk, price, code = row + + codes_in_use.add(code) + + # Copy the 'price' field back into the 'cost' field + response = cursor.execute(f'UPDATE part_supplierpricebreak set cost={price} where id={pk};') + + # Keep a dict of which currency objects map to which code + code_map = {} + + # For each currency code in use, check if we have a matching Currency object + for code in codes_in_use: + response = cursor.execute(f'SELECT id, suffix from common_currency where suffix="{code}";') + row = response.fetchone() + + if row is not None: + # A match exists! + pk, suffix = row + code_map[suffix] = pk + else: + # No currency object exists! + description = CURRENCIES[code] + + # Create a new object in the database + print(f"Creating new Currency object for {code}") + + # Construct a query to create a new Currency object + query = f'INSERT into common_currency (symbol, suffix, description, value, base) VALUES ("$", "{code}", "{description}", 1.0, False);' + + response = cursor.execute(query) + + code_map[code] = cursor.lastrowid + + # Ok, now we know how each suffix maps to a Currency object + for suffix in code_map.keys(): + pk = code_map[suffix] + + # Update the table to point to the Currency objects + print(f"Currency {suffix} -> pk {pk}") + + response = cursor.execute(f'UPDATE part_supplierpricebreak set currency_id={pk} where price_currency="{suffix}";') + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0025_auto_20201110_1001'), + ] + + operations = [ + migrations.RunPython(migrate_currencies, reverse_code=reverse_currencies), + ] From 1fc2ef5f180c04342d44744d9870dc32fa6b24d3 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 10 Nov 2020 22:31:46 +1100 Subject: [PATCH 24/52] Custom migration for PartSellPriceBreak --- .../migrations/0055_auto_20201110_1001.py | 24 +++ .../migrations/0056_auto_20201110_1125.py | 141 ++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 InvenTree/part/migrations/0055_auto_20201110_1001.py create mode 100644 InvenTree/part/migrations/0056_auto_20201110_1125.py diff --git a/InvenTree/part/migrations/0055_auto_20201110_1001.py b/InvenTree/part/migrations/0055_auto_20201110_1001.py new file mode 100644 index 0000000000..e9649c985e --- /dev/null +++ b/InvenTree/part/migrations/0055_auto_20201110_1001.py @@ -0,0 +1,24 @@ +# Generated by Django 3.0.7 on 2020-11-10 10:01 + +from django.db import migrations +import djmoney.models.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0054_auto_20201109_1246'), + ] + + operations = [ + migrations.AddField( + model_name='partsellpricebreak', + name='price', + field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency='USD', help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price'), + ), + migrations.AddField( + model_name='partsellpricebreak', + name='price_currency', + field=djmoney.models.fields.CurrencyField(choices=[('AUD', 'Australian Dollar'), ('CAD', 'Canadian Dollar'), ('EUR', 'Euro'), ('NZD', 'New Zealand Dollar'), ('GBP', 'Pound Sterling'), ('USD', 'US Dollar'), ('JPY', 'Yen')], default='USD', editable=False, max_length=3), + ), + ] diff --git a/InvenTree/part/migrations/0056_auto_20201110_1125.py b/InvenTree/part/migrations/0056_auto_20201110_1125.py new file mode 100644 index 0000000000..e15409daec --- /dev/null +++ b/InvenTree/part/migrations/0056_auto_20201110_1125.py @@ -0,0 +1,141 @@ +# Generated by Django 3.0.7 on 2020-11-10 11:25 + +from django.db import migrations + +from moneyed import CURRENCIES +from django.db import migrations, connection +from company.models import SupplierPriceBreak + + +def migrate_currencies(apps, schema_editor): + """ + Migrate from the 'old' method of handling currencies, + to the new method which uses the django-money library. + + Previously, we created a custom Currency model, + which was very simplistic. + + Here we will attempt to map each existing "currency" reference + for the SupplierPriceBreak model, to a new django-money compatible currency. + """ + + print("Updating currency references for SupplierPriceBreak model...") + + # A list of available currency codes + currency_codes = CURRENCIES.keys() + + cursor = connection.cursor() + + # The 'suffix' field denotes the currency code + response = cursor.execute('SELECT id, suffix, description from common_currency;').fetchall() + + remap = {} + + for index, row in enumerate(response): + pk, suffix, description = row + + suffix = suffix.strip().upper() + + if suffix not in currency_codes: + print("Missing suffix:", suffix) + + while suffix not in currency_codes: + # Ask the user to input a valid currency + print(f"Could not find a valid currency matching '{suffix}'.") + print("Please enter a valid currency code") + suffix = str(input("> ")).strip() + + if pk not in remap.keys(): + remap[pk] = suffix + + # Now iterate through each PartSellPriceBreak and update the rows + response = cursor.execute('SELECT id, cost, currency_id, price, price_currency from part_partsellpricebreak;').fetchall() + + count = 0 + + for index, row in enumerate(response): + pk, cost, currency_id, price, price_currency = row + + # Copy the 'cost' field across to the 'price' field + response = cursor.execute(f'UPDATE part_partsellpricebreak set price={cost} where id={pk};') + + # Extract the updated currency code + currency_code = remap.get(currency_id, 'USD') + + # Update the currency code + response = cursor.execute(f'UPDATE part_partsellpricebreak set price_currency= "{currency_code}" where id={pk};') + + count += 1 + + print(f"Updated {count} SupplierPriceBreak rows") + +def reverse_currencies(apps, schema_editor): + """ + Reverse the "update" process. + + Here we may be in the situation that the legacy "Currency" table is empty, + and so we have to re-populate it based on the new price_currency codes. + """ + + print("Reversing currency migration...") + + cursor = connection.cursor() + + # Extract a list of currency codes which are in use + response = cursor.execute(f'SELECT id, price, price_currency from part_partsellpricebreak;').fetchall() + + codes_in_use = set() + + for index, row in enumerate(response): + pk, price, code = row + + codes_in_use.add(code) + + # Copy the 'price' field back into the 'cost' field + response = cursor.execute(f'UPDATE part_partsellpricebreak set cost={price} where id={pk};') + + # Keep a dict of which currency objects map to which code + code_map = {} + + # For each currency code in use, check if we have a matching Currency object + for code in codes_in_use: + response = cursor.execute(f'SELECT id, suffix from common_currency where suffix="{code}";') + row = response.fetchone() + + if row is not None: + # A match exists! + pk, suffix = row + code_map[suffix] = pk + else: + # No currency object exists! + description = CURRENCIES[code] + + # Create a new object in the database + print(f"Creating new Currency object for {code}") + + # Construct a query to create a new Currency object + query = f'INSERT into common_currency (symbol, suffix, description, value, base) VALUES ("$", "{code}", "{description}", 1.0, False);' + + response = cursor.execute(query) + + code_map[code] = cursor.lastrowid + + # Ok, now we know how each suffix maps to a Currency object + for suffix in code_map.keys(): + pk = code_map[suffix] + + # Update the table to point to the Currency objects + print(f"Currency {suffix} -> pk {pk}") + + response = cursor.execute(f'UPDATE part_partsellpricebreak set currency_id={pk} where price_currency="{suffix}";') + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0055_auto_20201110_1001'), + ] + + operations = [ + migrations.RunPython(migrate_currencies, reverse_code=reverse_currencies), + ] From 4dff18e4a66608d0a046fae7bc8d1f3a258b9549 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 11 Nov 2020 00:21:06 +1100 Subject: [PATCH 25/52] Remove common_currency model entirely - A lot of views / pages / etc needed to be updated too - Now uses django-money fields entirely - Create a manual rate exchange backend (needs more work!) --- InvenTree/InvenTree/exchange.py | 21 ++++ InvenTree/InvenTree/settings.py | 4 + InvenTree/InvenTree/urls.py | 1 - InvenTree/common/admin.py | 7 +- InvenTree/common/api.py | 30 ----- InvenTree/common/forms.py | 16 +-- .../common/migrations/0009_delete_currency.py | 18 +++ InvenTree/common/models.py | 105 +++------------- InvenTree/common/serializers.py | 19 --- InvenTree/common/tests.py | 2 +- InvenTree/common/urls.py | 12 -- InvenTree/common/views.py | 26 +--- InvenTree/company/admin.py | 5 +- InvenTree/company/fixtures/price_breaks.yaml | 14 +-- InvenTree/company/forms.py | 5 +- ...0027_remove_supplierpricebreak_currency.py | 17 +++ .../0028_remove_supplierpricebreak_cost.py | 17 +++ InvenTree/company/models.py | 16 +-- InvenTree/company/serializers.py | 11 +- .../company/supplier_part_pricing.html | 11 +- InvenTree/company/tests.py | 10 ++ InvenTree/company/views.py | 17 +-- InvenTree/part/admin.py | 2 +- InvenTree/part/forms.py | 10 +- ...0057_remove_partsellpricebreak_currency.py | 17 +++ .../0058_remove_partsellpricebreak_cost.py | 17 +++ InvenTree/part/serializers.py | 11 +- .../part/templates/part/sale_prices.html | 15 +-- InvenTree/part/views.py | 35 ++---- .../InvenTree/settings/currency.html | 118 ------------------ .../templates/InvenTree/settings/tabs.html | 3 - InvenTree/users/models.py | 1 - requirements.txt | 3 +- 33 files changed, 194 insertions(+), 422 deletions(-) create mode 100644 InvenTree/InvenTree/exchange.py create mode 100644 InvenTree/common/migrations/0009_delete_currency.py create mode 100644 InvenTree/company/migrations/0027_remove_supplierpricebreak_currency.py create mode 100644 InvenTree/company/migrations/0028_remove_supplierpricebreak_cost.py create mode 100644 InvenTree/part/migrations/0057_remove_partsellpricebreak_currency.py create mode 100644 InvenTree/part/migrations/0058_remove_partsellpricebreak_cost.py delete mode 100644 InvenTree/templates/InvenTree/settings/currency.html diff --git a/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py new file mode 100644 index 0000000000..04ceabccd8 --- /dev/null +++ b/InvenTree/InvenTree/exchange.py @@ -0,0 +1,21 @@ +from djmoney.contrib.exchange.backends.base import BaseExchangeBackend + + +class InvenTreeManualExchangeBackend(BaseExchangeBackend): + """ + Backend for manually updating currency exchange rates + + See the documentation for django-money: https://github.com/django-money/django-money + + Specifically: https://github.com/django-money/django-money/tree/master/djmoney/contrib/exchange/backends + """ + + name = "inventree" + url = None + + def get_rates(self, **kwargs): + """ + Do not get any rates... + """ + + return {} diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 9c58686b56..7db24b8a9e 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -156,6 +156,7 @@ INSTALLED_APPS = [ 'django_tex', # LaTeX output 'django_admin_shell', # Python shell for the admin interface 'djmoney', # django-money integration + 'djmoney.contrib.exchange', # django-money exchange rates ] LOGGING = { @@ -360,6 +361,9 @@ CURRENCIES = CONFIG.get( ], ) +# TODO - Allow live web-based backends in the future +EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeManualExchangeBackend' + LOCALE_PATHS = ( os.path.join(BASE_DIR, 'locale/'), ) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index d729210235..ff008088ab 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -74,7 +74,6 @@ settings_urls = [ url(r'^theme/?', ColorThemeSelectView.as_view(), name='settings-theme'), url(r'^global/?', SettingsView.as_view(template_name='InvenTree/settings/global.html'), name='settings-global'), - url(r'^currency/?', SettingsView.as_view(template_name='InvenTree/settings/currency.html'), name='settings-currency'), url(r'^part/?', SettingsView.as_view(template_name='InvenTree/settings/part.html'), name='settings-part'), url(r'^stock/?', SettingsView.as_view(template_name='InvenTree/settings/stock.html'), name='settings-stock'), url(r'^build/?', SettingsView.as_view(template_name='InvenTree/settings/build.html'), name='settings-build'), diff --git a/InvenTree/common/admin.py b/InvenTree/common/admin.py index da12852d83..3edcd1fa8d 100644 --- a/InvenTree/common/admin.py +++ b/InvenTree/common/admin.py @@ -5,11 +5,7 @@ from django.contrib import admin from import_export.admin import ImportExportModelAdmin -from .models import Currency, InvenTreeSetting - - -class CurrencyAdmin(ImportExportModelAdmin): - list_display = ('symbol', 'suffix', 'description', 'value', 'base') +from .models import InvenTreeSetting class SettingsAdmin(ImportExportModelAdmin): @@ -17,5 +13,4 @@ class SettingsAdmin(ImportExportModelAdmin): list_display = ('key', 'value') -admin.site.register(Currency, CurrencyAdmin) admin.site.register(InvenTreeSetting, SettingsAdmin) diff --git a/InvenTree/common/api.py b/InvenTree/common/api.py index e1a9a0e3f0..8a2dfbd6a7 100644 --- a/InvenTree/common/api.py +++ b/InvenTree/common/api.py @@ -5,35 +5,5 @@ Provides a JSON API for common components. # -*- coding: utf-8 -*- from __future__ import unicode_literals -from rest_framework import permissions, generics, filters - -from django.conf.urls import url - -from .models import Currency -from .serializers import CurrencySerializer - - -class CurrencyList(generics.ListCreateAPIView): - """ API endpoint for accessing a list of Currency objects. - - - GET: Return a list of Currencies - - POST: Create a new currency - """ - - queryset = Currency.objects.all() - serializer_class = CurrencySerializer - - permission_classes = [ - permissions.IsAuthenticated, - ] - - filter_backends = [ - filters.OrderingFilter, - ] - - ordering_fields = ['suffix', 'value'] - - common_api_urls = [ - url(r'^currency/?$', CurrencyList.as_view(), name='api-currency-list'), ] diff --git a/InvenTree/common/forms.py b/InvenTree/common/forms.py index ba6289221e..84e44f3a31 100644 --- a/InvenTree/common/forms.py +++ b/InvenTree/common/forms.py @@ -7,21 +7,7 @@ from __future__ import unicode_literals from InvenTree.forms import HelperForm -from .models import Currency, InvenTreeSetting - - -class CurrencyEditForm(HelperForm): - """ Form for creating / editing a currency object """ - - class Meta: - model = Currency - fields = [ - 'symbol', - 'suffix', - 'description', - 'value', - 'base' - ] +from .models import InvenTreeSetting class SettingEditForm(HelperForm): diff --git a/InvenTree/common/migrations/0009_delete_currency.py b/InvenTree/common/migrations/0009_delete_currency.py new file mode 100644 index 0000000000..3e2edce4c7 --- /dev/null +++ b/InvenTree/common/migrations/0009_delete_currency.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2020-11-10 11:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0027_remove_supplierpricebreak_currency'), + ('part', '0057_remove_partsellpricebreak_currency'), + ('common', '0008_remove_inventreesetting_description'), + ] + + operations = [ + migrations.DeleteModel( + name='Currency', + ), + ] diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 966ed48f2f..4c184ad392 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -7,16 +7,18 @@ These models are 'generic' and do not fit a particular business logic object. from __future__ import unicode_literals import os -import decimal from django.db import models from django.conf import settings import djmoney.settings from djmoney.models.fields import MoneyField +from djmoney.money import Money +from djmoney.contrib.exchange.models import convert_money +from djmoney.contrib.exchange.exceptions import MissingRate from django.utils.translation import ugettext as _ -from django.core.validators import MinValueValidator, MaxValueValidator +from django.core.validators import MinValueValidator from django.core.exceptions import ValidationError import InvenTree.helpers @@ -455,74 +457,6 @@ class InvenTreeSetting(models.Model): return InvenTree.helpers.str2bool(self.value) -class Currency(models.Model): - """ - A Currency object represents a particular unit of currency. - Each Currency has a scaling factor which relates it to the base currency. - There must be one (and only one) currency which is selected as the base currency, - and each other currency is calculated relative to it. - - Attributes: - symbol: Currency symbol e.g. $ - suffix: Currency suffix e.g. AUD - description: Long-form description e.g. "Australian Dollars" - value: The value of this currency compared to the base currency. - base: True if this currency is the base currency - - """ - - symbol = models.CharField(max_length=10, blank=False, unique=False, help_text=_('Currency Symbol e.g. $')) - - suffix = models.CharField(max_length=10, blank=False, unique=True, help_text=_('Currency Suffix e.g. AUD')) - - description = models.CharField(max_length=100, blank=False, help_text=_('Currency Description')) - - value = models.DecimalField(default=1.0, max_digits=10, decimal_places=5, validators=[MinValueValidator(0.00001), MaxValueValidator(100000)], help_text=_('Currency Value')) - - base = models.BooleanField(default=False, help_text=_('Use this currency as the base currency')) - - class Meta: - verbose_name_plural = 'Currencies' - - def __str__(self): - """ Format string for currency representation """ - s = "{sym} {suf} - {desc}".format( - sym=self.symbol, - suf=self.suffix, - desc=self.description - ) - - if self.base: - s += " (Base)" - - else: - s += " = {v}".format(v=self.value) - - return s - - def save(self, *args, **kwargs): - """ Validate the model before saving - - - Ensure that there is only one base currency! - """ - - # If this currency is set as the base currency, ensure no others are - if self.base: - for cur in Currency.objects.filter(base=True).exclude(pk=self.pk): - cur.base = False - cur.save() - - # If there are no currencies set as the base currency, set this as base - if not Currency.objects.exclude(pk=self.pk).filter(base=True).exists(): - self.base = True - - # If this is the base currency, ensure value is set to unity - if self.base: - self.value = 1.0 - - super().save(*args, **kwargs) - - class PriceBreak(models.Model): """ Represents a PriceBreak model @@ -533,10 +467,6 @@ class PriceBreak(models.Model): quantity = InvenTree.fields.RoundingDecimalField(max_digits=15, decimal_places=5, default=1, validators=[MinValueValidator(1)]) - cost = InvenTree.fields.RoundingDecimalField(max_digits=10, decimal_places=5, validators=[MinValueValidator(0)]) - - currency = models.ForeignKey(Currency, blank=True, null=True, on_delete=models.SET_NULL) - price = MoneyField( max_digits=19, decimal_places=4, @@ -546,26 +476,21 @@ class PriceBreak(models.Model): help_text=_('Unit price at specified quantity'), ) - @property - def symbol(self): - return self.currency.symbol if self.currency else '' - - @property - def suffix(self): - return self.currency.suffix if self.currency else '' - - @property - def converted_cost(self): + def convert_to(self, currency_code): """ - Return the cost of this price break, converted to the base currency + Convert the unit-price at this price break to the specified currency code. + + Args: + currency_code - The currency code to convert to (e.g "USD" or "AUD") """ - scaler = decimal.Decimal(1.0) + try: + converted = convert_money(self.price, currency_code) + except MissingRate: + print(f"WARNING: No currency conversion rate available for {self.price_currency} -> {currency_code}") + return self.price.amount - if self.currency: - scaler = self.currency.value - - return self.cost * scaler + return converted.amount class ColorTheme(models.Model): diff --git a/InvenTree/common/serializers.py b/InvenTree/common/serializers.py index 73b4da8adf..99ac03cdfd 100644 --- a/InvenTree/common/serializers.py +++ b/InvenTree/common/serializers.py @@ -1,22 +1,3 @@ """ JSON serializers for common components """ - -from .models import Currency - -from InvenTree.serializers import InvenTreeModelSerializer - - -class CurrencySerializer(InvenTreeModelSerializer): - """ Serializer for Currency object """ - - class Meta: - model = Currency - fields = [ - 'pk', - 'symbol', - 'suffix', - 'description', - 'value', - 'base' - ] diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 323049f164..7aa0a3a894 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals from django.test import TestCase from django.contrib.auth import get_user_model -from .models import Currency, InvenTreeSetting +from .models import InvenTreeSetting class CurrencyTest(TestCase): diff --git a/InvenTree/common/urls.py b/InvenTree/common/urls.py index b5d6deadde..261ea1a691 100644 --- a/InvenTree/common/urls.py +++ b/InvenTree/common/urls.py @@ -2,17 +2,5 @@ URL lookup for common views """ -from django.conf.urls import url, include - -from . import views - -currency_urls = [ - url(r'^new/', views.CurrencyCreate.as_view(), name='currency-create'), - - url(r'^(?P\d+)/edit/', views.CurrencyEdit.as_view(), name='currency-edit'), - url(r'^(?P\d+)/delete/', views.CurrencyDelete.as_view(), name='currency-delete'), -] - common_urls = [ - url(r'currency/', include(currency_urls)), ] diff --git a/InvenTree/common/views.py b/InvenTree/common/views.py index ff72a44e3d..3bf3769231 100644 --- a/InvenTree/common/views.py +++ b/InvenTree/common/views.py @@ -8,37 +8,13 @@ from __future__ import unicode_literals from django.utils.translation import ugettext as _ from django.forms import CheckboxInput, Select -from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView +from InvenTree.views import AjaxUpdateView from InvenTree.helpers import str2bool from . import models from . import forms -class CurrencyCreate(AjaxCreateView): - """ View for creating a new Currency object """ - - model = models.Currency - form_class = forms.CurrencyEditForm - ajax_form_title = _('Create new Currency') - - -class CurrencyEdit(AjaxUpdateView): - """ View for editing an existing Currency object """ - - model = models.Currency - form_class = forms.CurrencyEditForm - ajax_form_title = _('Edit Currency') - - -class CurrencyDelete(AjaxDeleteView): - """ View for deleting an existing Currency object """ - - model = models.Currency - ajax_form_title = _('Delete Currency') - ajax_template_name = "common/delete_currency.html" - - class SettingEdit(AjaxUpdateView): """ View for editing an InvenTree key:value settings object, diff --git a/InvenTree/company/admin.py b/InvenTree/company/admin.py index 4a802c6a41..45dd769d67 100644 --- a/InvenTree/company/admin.py +++ b/InvenTree/company/admin.py @@ -13,7 +13,6 @@ from .models import SupplierPart from .models import SupplierPriceBreak from part.models import Part -from common.models import Currency class CompanyResource(ModelResource): @@ -75,8 +74,6 @@ class SupplierPriceBreakResource(ModelResource): part = Field(attribute='part', widget=widgets.ForeignKeyWidget(SupplierPart)) - currency = Field(attribute='currency', widget=widgets.ForeignKeyWidget(Currency)) - supplier_id = Field(attribute='part__supplier__pk', readonly=True) supplier_name = Field(attribute='part__supplier__name', readonly=True) @@ -98,7 +95,7 @@ class SupplierPriceBreakAdmin(ImportExportModelAdmin): resource_class = SupplierPriceBreakResource - list_display = ('part', 'quantity', 'cost') + list_display = ('part', 'quantity', 'price') admin.site.register(Company, CompanyAdmin) diff --git a/InvenTree/company/fixtures/price_breaks.yaml b/InvenTree/company/fixtures/price_breaks.yaml index 6ae8cce94c..dbcbacb017 100644 --- a/InvenTree/company/fixtures/price_breaks.yaml +++ b/InvenTree/company/fixtures/price_breaks.yaml @@ -7,21 +7,21 @@ fields: part: 1 quantity: 1 - cost: 10 + price: 10 - model: company.supplierpricebreak pk: 2 fields: part: 1 quantity: 5 - cost: 7.50 + price: 7.50 - model: company.supplierpricebreak pk: 3 fields: part: 1 quantity: 25 - cost: 3.50 + price: 3.50 # Price breaks for ACME0002 - model: company.supplierpricebreak @@ -29,14 +29,14 @@ fields: part: 2 quantity: 5 - cost: 7.00 + price: 7.00 - model: company.supplierpricebreak pk: 5 fields: part: 2 quantity: 50 - cost: 1.25 + price: 1.25 # Price breaks for ZERGLPHS - model: company.supplierpricebreak @@ -44,11 +44,11 @@ fields: part: 4 quantity: 25 - cost: 8 + price: 8 - model: company.supplierpricebreak pk: 7 fields: part: 4 quantity: 100 - cost: 1.25 \ No newline at end of file + price: 1.25 \ No newline at end of file diff --git a/InvenTree/company/forms.py b/InvenTree/company/forms.py index ac3cc69c99..9ebb8839f3 100644 --- a/InvenTree/company/forms.py +++ b/InvenTree/company/forms.py @@ -82,13 +82,10 @@ class EditPriceBreakForm(HelperForm): quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5) - cost = RoundingDecimalFormField(max_digits=10, decimal_places=5) - class Meta: model = SupplierPriceBreak fields = [ 'part', 'quantity', - 'cost', - 'currency', + 'price', ] diff --git a/InvenTree/company/migrations/0027_remove_supplierpricebreak_currency.py b/InvenTree/company/migrations/0027_remove_supplierpricebreak_currency.py new file mode 100644 index 0000000000..b2e23d7538 --- /dev/null +++ b/InvenTree/company/migrations/0027_remove_supplierpricebreak_currency.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.7 on 2020-11-10 11:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0026_auto_20201110_1011'), + ] + + operations = [ + migrations.RemoveField( + model_name='supplierpricebreak', + name='currency', + ), + ] diff --git a/InvenTree/company/migrations/0028_remove_supplierpricebreak_cost.py b/InvenTree/company/migrations/0028_remove_supplierpricebreak_cost.py new file mode 100644 index 0000000000..0522560f26 --- /dev/null +++ b/InvenTree/company/migrations/0028_remove_supplierpricebreak_cost.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.7 on 2020-11-10 11:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0027_remove_supplierpricebreak_currency'), + ] + + operations = [ + migrations.RemoveField( + model_name='supplierpricebreak', + name='cost', + ), + ] diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index b9fed2ee7b..75b765bb2e 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -350,7 +350,7 @@ class SupplierPart(models.Model): def unit_pricing(self): return self.get_price(1) - def get_price(self, quantity, moq=True, multiples=True): + def get_price(self, quantity, moq=True, multiples=True, currency=None): """ Calculate the supplier price based on quantity price breaks. - Don't forget to add in flat-fee cost (base_cost field) @@ -372,6 +372,10 @@ class SupplierPart(models.Model): pb_quantity = -1 pb_cost = 0.0 + if currency is None: + # Default currency selection + currency = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') + for pb in self.price_breaks.all(): # Ignore this pricebreak (quantity is too high) if pb.quantity > quantity: @@ -382,8 +386,9 @@ class SupplierPart(models.Model): # If this price-break quantity is the largest so far, use it! if pb.quantity > pb_quantity: pb_quantity = pb.quantity - # Convert everything to base currency - pb_cost = pb.converted_cost + + # Convert everything to the selected currency + pb_cost = pb.convert_to(currency) if pb_found: cost = pb_cost * quantity @@ -462,7 +467,4 @@ class SupplierPriceBreak(common.models.PriceBreak): db_table = 'part_supplierpricebreak' def __str__(self): - return "{mpn} - {cost} @ {quan}".format( - mpn=self.part.MPN, - cost=self.cost, - quan=self.quantity) + return f'{self.part.MPN} - {self.price} @ {self.quantity}' diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py index f6de7d4f50..4951bd3ad0 100644 --- a/InvenTree/company/serializers.py +++ b/InvenTree/company/serializers.py @@ -137,13 +137,9 @@ class SupplierPartSerializer(InvenTreeModelSerializer): class SupplierPriceBreakSerializer(InvenTreeModelSerializer): """ Serializer for SupplierPriceBreak object """ - symbol = serializers.CharField(read_only=True) - - suffix = serializers.CharField(read_only=True) - quantity = serializers.FloatField() - cost = serializers.FloatField() + price = serializers.CharField() class Meta: model = SupplierPriceBreak @@ -151,8 +147,5 @@ class SupplierPriceBreakSerializer(InvenTreeModelSerializer): 'pk', 'part', 'quantity', - 'cost', - 'currency', - 'symbol', - 'suffix', + 'price', ] diff --git a/InvenTree/company/templates/company/supplier_part_pricing.html b/InvenTree/company/templates/company/supplier_part_pricing.html index 6138669bc4..e665339968 100644 --- a/InvenTree/company/templates/company/supplier_part_pricing.html +++ b/InvenTree/company/templates/company/supplier_part_pricing.html @@ -76,18 +76,11 @@ $('#price-break-table').inventreeTable({ sortable: true, }, { - field: 'cost', + field: 'price', title: '{% trans "Price" %}', sortable: true, formatter: function(value, row, index) { - var html = ''; - - html += row.symbol || ''; - html += value; - - if (row.suffix) { - html += ' ' + row.suffix || ''; - } + var html = value; html += `
` diff --git a/InvenTree/company/tests.py b/InvenTree/company/tests.py index db515d3e59..1e57adcda3 100644 --- a/InvenTree/company/tests.py +++ b/InvenTree/company/tests.py @@ -6,6 +6,8 @@ from .models import Company, Contact, SupplierPart from .models import rename_company_image from part.models import Part +from InvenTree.exchange import InvenTreeManualExchangeBackend +from djmoney.contrib.exchange.models import Rate class CompanySimpleTest(TestCase): @@ -32,6 +34,14 @@ class CompanySimpleTest(TestCase): self.zerglphs = SupplierPart.objects.get(SKU='ZERGLPHS') self.zergm312 = SupplierPart.objects.get(SKU='ZERGM312') + InvenTreeManualExchangeBackend().update_rates() + + Rate.objects.create( + currency='AUD', + value='1.35', + backend_id='inventree', + ) + def test_company_model(self): c = Company.objects.get(name='ABC Co.') self.assertEqual(c.name, 'ABC Co.') diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py index dce341d184..abdad55322 100644 --- a/InvenTree/company/views.py +++ b/InvenTree/company/views.py @@ -12,12 +12,12 @@ from django.views.generic import DetailView, ListView, UpdateView from django.urls import reverse from django.forms import HiddenInput +from moneyed import CURRENCIES + from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView from InvenTree.helpers import str2bool from InvenTree.views import InvenTreeRoleMixin -from common.models import Currency - from .models import Company from .models import SupplierPart from .models import SupplierPriceBreak @@ -29,6 +29,8 @@ from .forms import CompanyImageForm from .forms import EditSupplierPartForm from .forms import EditPriceBreakForm +import common.models + class CompanyIndex(InvenTreeRoleMixin, ListView): """ View for displaying list of companies @@ -435,12 +437,11 @@ class PriceBreakCreate(AjaxCreateView): initials['part'] = self.get_part() - # Pre-select the default currency - try: - base = Currency.objects.get(base=True) - initials['currency'] = base - except Currency.DoesNotExist: - pass + default_currency = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') + currency = CURRENCIES.get(default_currency, None) + + if currency is not None: + initials['price'] = [1.0, currency] return initials diff --git a/InvenTree/part/admin.py b/InvenTree/part/admin.py index 7476197547..11dd5c8dc0 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -279,7 +279,7 @@ class PartSellPriceBreakAdmin(admin.ModelAdmin): class Meta: model = PartSellPriceBreak - list_display = ('part', 'quantity', 'cost', 'currency') + list_display = ('part', 'quantity', 'price',) admin.site.register(Part, PartAdmin) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index d72df4ed9f..c100b08b3e 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -19,8 +19,6 @@ from .models import PartParameterTemplate, PartParameter from .models import PartTestTemplate from .models import PartSellPriceBreak -from common.models import Currency - class PartModelChoiceField(forms.ModelChoiceField): """ Extending string representation of Part instance with available stock """ @@ -298,13 +296,10 @@ class PartPriceForm(forms.Form): help_text=_('Input quantity for price calculation') ) - currency = forms.ModelChoiceField(queryset=Currency.objects.all(), label='Currency', help_text=_('Select currency for price calculation')) - class Meta: model = Part fields = [ 'quantity', - 'currency', ] @@ -315,13 +310,10 @@ class EditPartSalePriceBreakForm(HelperForm): quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5) - cost = RoundingDecimalFormField(max_digits=10, decimal_places=5) - class Meta: model = PartSellPriceBreak fields = [ 'part', 'quantity', - 'cost', - 'currency', + 'price', ] diff --git a/InvenTree/part/migrations/0057_remove_partsellpricebreak_currency.py b/InvenTree/part/migrations/0057_remove_partsellpricebreak_currency.py new file mode 100644 index 0000000000..974aecef4b --- /dev/null +++ b/InvenTree/part/migrations/0057_remove_partsellpricebreak_currency.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.7 on 2020-11-10 11:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0056_auto_20201110_1125'), + ] + + operations = [ + migrations.RemoveField( + model_name='partsellpricebreak', + name='currency', + ), + ] diff --git a/InvenTree/part/migrations/0058_remove_partsellpricebreak_cost.py b/InvenTree/part/migrations/0058_remove_partsellpricebreak_cost.py new file mode 100644 index 0000000000..dcf625aa6f --- /dev/null +++ b/InvenTree/part/migrations/0058_remove_partsellpricebreak_cost.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.7 on 2020-11-10 11:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0057_remove_partsellpricebreak_currency'), + ] + + operations = [ + migrations.RemoveField( + model_name='partsellpricebreak', + name='cost', + ), + ] diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index f6eb8dc95b..14a1147b67 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -84,13 +84,9 @@ class PartSalePriceSerializer(InvenTreeModelSerializer): Serializer for sale prices for Part model. """ - symbol = serializers.CharField(read_only=True) - - suffix = serializers.CharField(read_only=True) - quantity = serializers.FloatField() - cost = serializers.FloatField() + price = serializers.CharField() class Meta: model = PartSellPriceBreak @@ -98,10 +94,7 @@ class PartSalePriceSerializer(InvenTreeModelSerializer): 'pk', 'part', 'quantity', - 'cost', - 'currency', - 'symbol', - 'suffix', + 'price', ] diff --git a/InvenTree/part/templates/part/sale_prices.html b/InvenTree/part/templates/part/sale_prices.html index 8d3cc61afd..033f280da8 100644 --- a/InvenTree/part/templates/part/sale_prices.html +++ b/InvenTree/part/templates/part/sale_prices.html @@ -10,7 +10,9 @@
- +
@@ -81,18 +83,11 @@ $('#price-break-table').inventreeTable({ sortable: true, }, { - field: 'cost', + field: 'price', title: '{% trans "Price" %}', sortable: true, formatter: function(value, row, index) { - var html = ''; - - html += row.symbol || ''; - html += value; - - if (row.suffix) { - html += ' ' + row.suffix || ''; - } + var html = value; html += `
` diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 2504b056c5..9b5aed0ddb 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -16,6 +16,8 @@ from django.forms.models import model_to_dict from django.forms import HiddenInput, CheckboxInput from django.conf import settings +from moneyed import CURRENCIES + import os from rapidfuzz import fuzz @@ -28,7 +30,7 @@ from .models import match_part_names from .models import PartTestTemplate from .models import PartSellPriceBreak -from common.models import Currency, InvenTreeSetting +from common.models import InvenTreeSetting from company.models import SupplierPart from . import forms as part_forms @@ -1860,19 +1862,12 @@ class PartPricing(AjaxView): if quantity < 1: quantity = 1 - if currency is None: - # No currency selected? Try to select a default one - try: - currency = Currency.objects.get(base=1) - except Currency.DoesNotExist: - currency = None + # TODO - Capacity for price comparison in different currencies + currency = None # Currency scaler scaler = Decimal(1.0) - if currency is not None: - scaler = Decimal(currency.value) - part = self.get_part() ctx = { @@ -1942,13 +1937,8 @@ class PartPricing(AjaxView): except ValueError: quantity = 1 - try: - currency_id = int(self.request.POST.get('currency', None)) - - if currency_id: - currency = Currency.objects.get(pk=currency_id) - except (ValueError, Currency.DoesNotExist): - currency = None + # TODO - How to handle pricing in different currencies? + currency = None # Always mark the form as 'invalid' (the user may wish to keep getting pricing data) data = { @@ -2393,12 +2383,11 @@ class PartSalePriceBreakCreate(AjaxCreateView): initials['part'] = self.get_part() - # Pre-select the default currency - try: - base = Currency.objects.get(base=True) - initials['currency'] = base - except Currency.DoesNotExist: - pass + default_currency = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') + currency = CURRENCIES.get(default_currency, None) + + if currency is not None: + initials['price'] = [1.0, currency] return initials diff --git a/InvenTree/templates/InvenTree/settings/currency.html b/InvenTree/templates/InvenTree/settings/currency.html deleted file mode 100644 index 444dd96c66..0000000000 --- a/InvenTree/templates/InvenTree/settings/currency.html +++ /dev/null @@ -1,118 +0,0 @@ -{% extends "InvenTree/settings/settings.html" %} -{% load i18n %} - -{% block subtitle %} -{% trans "General Settings" %} -{% endblock %} - -{% block tabs %} -{% include "InvenTree/settings/tabs.html" with tab='currency' %} -{% endblock %} - -{% block settings %} - -

{% trans "Currencies" %}

- -
- -
- -
-
-{% endblock %} - -{% block js_ready %} -{{ block.super }} - - $("#currency-table").inventreeTable({ - url: "{% url 'api-currency-list' %}", - queryParams: { - ordering: 'suffix' - }, - formatNoMatches: function() { return "No currencies found"; }, - rowStyle: function(row, index) { - if (row.base) { - return {classes: 'basecurrency'}; - } else { - return {}; - } - }, - columns: [ - { - field: 'pk', - title: 'ID', - visible: false, - switchable: false, - }, - { - field: 'symbol', - title: 'Symbol', - }, - { - field: 'suffix', - title: 'Currency', - sortable: true, - }, - { - field: 'description', - title: 'Description', - sortable: true, - }, - { - field: 'value', - title: 'Value', - sortable: true, - formatter: function(value, row, index, field) { - if (row.base) { - return "Base Currency"; - } else { - return value; - } - } - }, - { - formatter: function(value, row, index, field) { - - var bEdit = ""; - var bDel = ""; - - var html = "
" + bEdit + bDel + "
"; - - return html; - } - } - ] - }); - - $("#currency-table").on('click', '.cur-edit', function() { - var button = $(this); - var url = "/common/currency/" + button.attr('pk') + "/edit/"; - - launchModalForm(url, { - success: function() { - $("#currency-table").bootstrapTable('refresh'); - }, - }); - }); - - $("#currency-table").on('click', '.cur-delete', function() { - var button = $(this); - var url = "/common/currency/" + button.attr('pk') + "/delete/"; - - launchModalForm(url, { - success: function() { - $("#currency-table").bootstrapTable('refresh'); - }, - }); - }); - - $("#new-currency").click(function() { - launchModalForm("{% url 'currency-create' %}", { - success: function() { - $("#currency-table").bootstrapTable('refresh'); - }, - }); - }); - -{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/settings/tabs.html b/InvenTree/templates/InvenTree/settings/tabs.html index d104908c49..a278c56325 100644 --- a/InvenTree/templates/InvenTree/settings/tabs.html +++ b/InvenTree/templates/InvenTree/settings/tabs.html @@ -15,9 +15,6 @@
  • {% trans "Global" %}
  • - - {% trans "Currency" %} - {% trans "Parts" %} diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index 98efb14764..a5b9021807 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -102,7 +102,6 @@ class RuleSet(models.Model): # Models which currently do not require permissions 'common_colortheme', - 'common_currency', 'common_inventreesetting', 'company_contact', 'label_stockitemlabel', diff --git a/requirements.txt b/requirements.txt index bc5fff38cc..9823b9d1fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,5 +27,6 @@ django-weasyprint==1.0.1 # HTML PDF export django-debug-toolbar==2.2 # Debug / profiling toolbar django-admin-shell==0.1.2 # Python shell for the admin interface django-money==1.1 # Django app for currency management +certifi # Certifi is (most likely) installed through one of the requirements above -inventree # Install the latest version of the InvenTree API python library \ No newline at end of file +inventree # Install the latest version of the InvenTree API python library From a19cf1f27ab54c4d291e06778d77c47db1e88c6a Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 11 Nov 2020 00:26:59 +1100 Subject: [PATCH 26/52] PEP fixes --- InvenTree/common/models.py | 1 - InvenTree/common/tests.py | 13 ------------- InvenTree/company/tests.py | 1 + 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 4c184ad392..4ebd60e8b0 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -13,7 +13,6 @@ from django.conf import settings import djmoney.settings from djmoney.models.fields import MoneyField -from djmoney.money import Money from djmoney.contrib.exchange.models import convert_money from djmoney.contrib.exchange.exceptions import MissingRate diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 7aa0a3a894..96c6bdc093 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -7,19 +7,6 @@ from django.contrib.auth import get_user_model from .models import InvenTreeSetting -class CurrencyTest(TestCase): - """ Tests for Currency model """ - - fixtures = [ - 'currency', - ] - - def test_currency(self): - # Simple test for now (improve this later!) - - self.assertEqual(Currency.objects.count(), 2) - - class SettingsTest(TestCase): """ Tests for the 'settings' model diff --git a/InvenTree/company/tests.py b/InvenTree/company/tests.py index 1e57adcda3..24c7e3a20f 100644 --- a/InvenTree/company/tests.py +++ b/InvenTree/company/tests.py @@ -9,6 +9,7 @@ from part.models import Part from InvenTree.exchange import InvenTreeManualExchangeBackend from djmoney.contrib.exchange.models import Rate + class CompanySimpleTest(TestCase): fixtures = [ From 5567ad07fdac3c25fad79971dec0890a2c2eaa43 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 11 Nov 2020 00:31:39 +1100 Subject: [PATCH 27/52] Update tests and translations --- InvenTree/locale/de/LC_MESSAGES/django.mo | Bin 46068 -> 45419 bytes InvenTree/locale/de/LC_MESSAGES/django.po | 836 +++++++++++----------- InvenTree/locale/en/LC_MESSAGES/django.po | 775 ++++++++++---------- InvenTree/locale/es/LC_MESSAGES/django.po | 775 ++++++++++---------- InvenTree/users/models.py | 2 + 5 files changed, 1182 insertions(+), 1206 deletions(-) diff --git a/InvenTree/locale/de/LC_MESSAGES/django.mo b/InvenTree/locale/de/LC_MESSAGES/django.mo index ccc97446bab5beb633a0e87b02aca50649994afc..4d57b3c49d2dc0d1dad23269c4ccba3b011a5fa5 100644 GIT binary patch delta 14450 zcmZA62Xv3uAII@0k%)*8BM}i2u_gAV_6%yot`RH59*tGwv0|ilq-xdPR4b)LOHrvB zMb&7jTBSy{swl1czdyfwo#XtUbFSC-j_2;@NB{r7ZhPOm?(JU5<&);{Ec14paQrKe z6aZZ#}spI@z)p2s*Gt7+s)f^`q24QwAfPol;YF7gbU^5KEL|Z-;L&>LNPTYpM z9mjR{+k)@VpNg9pgby$jU!os|RyPAjqA&TiAG#|CY zt5Fl#k2x6MIZD8eI~Q%mWz8-Yn(+&(Kg-uy2cznvP!lU>%j=*f)B^QhUt2x`HIR!siUr77 zJDX8E^abiJWz=H-weph`XeDQ?m#_r+U$7!(ukAR6u|7s)U)01FT31@Pp>}K!s=pK1 z7|$c8<`k&oIAyRCs(wZt_FpqvKmixuS&BNV9jFz5iDCFHYDITY?f*etN}sxB<=L#E zsE&)E>dT-mXBAX`jcvXiY5{#*0&UH3YcgtpC8(`lWBnM_@t4+PsITLD)Ih&sFg`|2 zIDjjz`eGP?bx`H;s3Um?L(pAFpcglzUN~UAhHv?5hwXcQRv9_qQPqg_Y z)K1N^<;yTX`HiTB9!BlR1*9L>xlK@zf)}R132$J|umWla8rgh1R0sV~D;S6BC>1rK zwW!Ou2i5K@YC*SA?VN@tAB1XG5<~R;H?Re7*#=3dEuDkIl@rXWh{gRuoCJB<52x~#oUbV^da!UF{q9wSm&Z{`%3F^)JmVCKA%9Y zwkDPbwZdYkOH~E6a}7}a#i1tJ8+Bywp!%7IuC{(XfnNLyHRCfje-kypLtCDyiFq*u zwc;35hfPrv=!R-P7&WkK^Yc+hwg%OHFP6qrP1t`8_`)^}YHAurS!M4C&Wt=eK6?6dh(sEJ%f4gAE` zXKrq8Zy0L8N~no6LA7g-8aM&7ViKw!7u7xuHNj1&9d&mR_!6A91?N#)`4BmF=NVSP zx-HBl8jbp0n2Ry^AqL==)l$Pje3 zB{OZs0@MUnVGi7my0u@RCUgdML_u+80>P*qD}~yzR#+MnQ2nN&Cb}Gh@MF}34x#7H z#j*c7!}AoVW8T<>boC`8h9bPD7np3hF4*tgCJLcANhUwbfstw)lJ07GJaV zk5K)*M77J>$+Qc@0Db?(33Lf7VmYjV+TwwzOEeSJ@mAEefc63ARBWegC})DqtdNCzhc)-iEqdpQ9#l40Y))pa!^xYIon(|7-Ps z%hZQp2=#?f{Zzxe*cdg@?&y{#NG8zPrlWqr&R|LW10yl4i@8KKQ5|)|d^iv_;bhch zdJn^JD;C8Jtc>@u3`TV|3uuGtze`v4Ut5|?fiB%5EQOz-X81Ge2PShjQy+~QusQ0E zbhYKZPz)dQlQ)W4eG_8P!qd@TG8LAt<2rs zth5wr>+7KINGsG4jX-_AE@}ZYu^jF|{ghwD7|arH-mB~qgi_E6wWY(cD!z;1co?Wl@)_CTb_!q9)Q0^*b^a)qWn<#N`;R@Bbo!elUD`nX~do zZGDiREKe>BS^%GI0m&-+b}=wMYTJRRq(#e7w&E9t7E*r|Mmo$@h;Rr z-=Mzl8>p2%LEUceKISXQi^^BF`DW;u2x@|3tSP98Ek_?bjhf&&)P!zee#Upg63pjN z1=Udx)BwX!9VTNDT#UMmpJQ#jfI6~}zUF_Jh(PUJE7b2pcWZytz)7g1n}Q871zinr zia?j}8fxpFqh=V=&z#wtsENIW+R_22J2DA1-~#l+Rj7eBq0V|YY9fa*Gk%MO@f>PF z&-$_dz61pm&5K1)E3bfhu?gxZ;!p!7*!(Ee(ab_kAPx0-u0u_9pDjO*n#hl+ox5%8 zz5AOT%hjL#FGNLs3N*7ms267125G1rIfA+)*H9~bjB1x@fH|Td%tAg2RUd7wj@q#% zsD*Vy^)muB@#!vsR+wfBHeoUHpJD;Lg6i;J)aA@N&`hWZ`jfAV8n_v%U0c*bdSVPF zqt1FK>N9_e%-zW|$n@_{Bv8l8Q9nGpu{1uwq8L8d{CmDW@&!3VFc#0CCXj!KnLtt0 z3d^7-&;WI0{cQah)KO1HO=Jer-gVXyXb1M7w(5k| zwX1+Swve{Izb3Igy2Y9;=o z%-?SLQ9Do*wUvFaB)V80H={1$B~*vMU?%*-mj8wNq4^IraN*HroJOdHw;9d;>xID- zcvgTq>+v=}8+F!;Q4?He%RjR2Lv@saTIqFLeh;ysGXUKI>Lom7q4JNjCjY4=e8wK z!yXud15igW8P!oL>U-T_%MYVga1Qm}Wz@uPp?2tzt$&HSq?yN=iDpCf8)ow{$d0>C z4FYX>BU{lPH9&9F3jX&jIYT)CjOM3yekS8`DINtn_#h~irFcODj zZpL>O66lOJ*ajIGMgA8ojaep``YNc;umc8Scht%US;wLVo{o8N1!`eCF%l2i`WvX7 zeU8D5?*vXXTU;1bQ5W@MThuRKZ(E*(>Uai*;&Rl0yRZPBz=C)eRqs#d;TVPLSN~(T zb}|n0Vj{XKm_(qC-b1~x9d(I{RLzuOJb% zpwXzKoq;;Cxs%v`t!OC)dSM6Z%y*#%`WnmPCDc&_Og1}E9W}w0sE#|L7SazjfeEOA zQ&9aaLG`y4wa^3B^OISLw(d6y^g_TC(@_{|>&u}gSkIPsKy^3>i{nJpR&PMHKY*Ih zX;izb)<01@=rh%{&x>Wqmvag9VmzwBFw}rkQAhDUYDK$j{UOxWokI<94fPp5LS4=m zsD=1WGnY6I7A9X6wa_l89UY5WfV-JM6F7rf$#o3Fe^8%UNV1twA=H3XP+Qm(b&29p z?~O(s!F*JI8&F&RsjWY5y@?T&zd#n~It8Yij%uLicA|D75!LZH)PRdnFK)$>cpUTL zV^sSbGt2~|Py<&&O|U8Iy&kCclTbUf6vG+c*-YR|L58P*&j@w7?qF^F6Dwi)nPw~d zqB=@Jm9Ims_y>%{yZ8nM%rf;&u^jmcsH6K3HL<-I$oS581UmDpsEIsAHT(y4nfy}B zit?iFLNsQhn?2V)e@K@Ip3`r}d5d*7j3jNnIt66p1=`Fp+;>az4k zUBby&2tPn=-Io}Er%=Cymrz@N2X)zAppLB2Y%_2*)DE>qEwraCA2pl(*QJ_Efo8l4 zJp*7P@~5yQ2G21Qs)H5CcS8*@A2sks%!>Q4IA)-Z=mBQNEOSkL4%EWJP&-p?F8i+u zbfG|7Js35^38+i85H-MhtciQj2Op!plIN(7g6EkDRm1Y+TcM66#@4dT98Pq$3v6_dke0FU&-8;kKR}^jU#7#Cx$>1sH_5Pf!fOUs4eV)!8iofZW`(+mZ5fJi*+xm{V~*l z7j5}NRC}-Y%n<~k+7&|2fB%;y&}FHI)vzU&!WpO)??m0!OPCvfL2dmrYrtYNKmjaA zc?_z3H`GG<+4>QvaVBFfoV%F)*X3JHK?B@?+JU>M%kl^{Q=j*ZxlkQOqUxh<`5UMe zHbV{28Fgvr-rg36`b&{zI2!<^+1C_VNqIRw)HpCRHj;Bx)^){WmpWyt< z&F!s)x@2*v%i0OGGviQ~ZaV6y=Aw>b4Qgk$p>}u=>V5YRfnGd{VR#nx!UNP6{*9WT z&k8g1JgBoTjOws5=EwS|fxDxAa{FQh9Bf^Mx*KP$J|B1%>^hZA;IzX~8oZ6Qa5n0@ zJ%*aV@2E@j5({9imBwJyAO_9<}B3u{5qkE#M?Z===YfKtG``Q5_Xs zWxnIGs25|=(=qC&xF>4m38(=^pmuON>QXL1O?VAz!aGszj-cNA0UO{gEW!9rsnurS zwx~1dh8nmxs^bx;%QOMi(FP30z1GuMmHaK#zy;Ts9VlasMJ=!o7Q_^c!p-RRB{)H# z4lAxT4PsHBO(LqpNvK;sA1mTU%!ijy9Y03BpMRZcR~`$KZ;Ewr5Nc<)p>|+5Hpj#3 z*ngd6j`ij6A{$qv+x?6>)EQ3L*je)v0TLQk!}o6J{~ z8#7ZLfyJ?ywWUj-nT~OWkU z=&{vztC>(R>PVweJ60d*$938fXhsRPVhmOwpMvV>3+qXYB>xkZ#Q#tOme^)iS`oD) zHL)l*#d0_V>*Gq)(O$=C=(SzHADsVmfjd3P+t`6;uZ;P+J_2T5%HU z%w5z3W}-gV#i)LEpbutXHaw1+z(rKM>!|miqpM4j>0`5n0T@j_x3vy>k?+ADC-4Cd z!>QihPy_pD@Y&>7<0$IOkhGHpaWv)Zyyq!z-@W9;{%4}-9F<>?YLnii@+0g@ zN+j_&rf0VOi1kb%eM1~eyP>wM17$9GUC`bbV9UvRp87U_kz8xyfwYOkH+255p1c(N zN9M;@1sFlY0+hdpT}ZE<&j^~^%w6gm*!TnDYNS_BIo@eW{vFa1`+n}%>QmjgoU5Y@q%Z0^Fwdc>t{CR+~inP)F@xq0+ZE<4#-SC`zJM4y0lpR8SMO|#W!I;aN z{a-_Hg9<%!4bI=zw|P-d9rUse|IF3R;& zt>;t9-Xlesth0jnL(+HT^U?MJ-X~2U@we}*$8~-pyhy_ja5@dYBn~7UCf}31o?fKN zq<<(cM=DLyGu5j9n?_G9+rAz7k>tDMC!~GEm292by+yiBYOM1wq7oi|8ZNbshLX=s zyp*y57)csQD&=X(ziKg_x?+^+DMhS@Uvke=)#jC%_gcJ+_*c`!`2$bt`=3wHnxuch z6n?GoY4Rn=FG7C?T7%heI%Q9Y^$&^$bye^_HX_}l{5&ZWadyi6NFNjH z8Aj~+{qwU0D*T;vnS3TH%MynWcP7@;h?JT9Q1TP-eH=qsC1O3@4NgOwS2m0|ns#xv zkG16WUqaXMsU6>0P9}!Jf_M_cX}k=Jk^h{ug;>uFgHwqEQ%%H3;>7^<(ejPHcsO(Ez89U+~QaWiKc|D(#n%K_rQ+9*+Ys&hQ zs*=uf7)*-^ z#J>}d!&c-65HBLmO5BX}H~IPGH{dmro?SSNGCgk+A0uhGjVKEwT_yh$Nly#?+w2Iz zAWzo(kziwgd?WQ?g$Or**P4+kkn~ixua~hNpzH~8JoWpD>yz5py6RSnJpX)?&nP=h znoaxR*i?nDJu``CP(Fzi@anQA(ReildWI7(RzN-y3M%I5O!B5q&n;3D;@>XY z&%Eb4?-1m(jUD2KG)gA1B4-I{0ck5K7pW&n&m_;E{Pk}Q!j5(#wO;jQ>-zj(zA5>1 zTXx<2?Zj=MpcXIuKzjSN7wQo&r)(YRAZgHRb(hIUlRrqVSd|2msSL3iA3E6UUGPvTOPg<((I$syu)l)Y=)g%D3B>8VTlmv#%W9O-@H zPw*(_K|QBzKZ>i7W|Q12{P7DFE$QfrZSW9FlK&5%QQr}>;aJiaq%)+qC>uj+O}p*n z+v2OI1aU9wYEm#B=cuk;uc0H8B4dY)N*K~^L_&g7uXj@aQ3>fgswD)ZmuR@bD?O@l z9k2A^O=7&$cf}s^N>6USEi}DjpAy;9Hw+t=Bfb2j3ts7cr&RSy^-s=~erReL@AQ|+ WSG>|6&e$HFet2C-_S8)25B~?${`Hpt delta 15016 zcmZA72Xs|M9>?(u1QKcpA#{=lp#(x2MS2U(2vQ@xh2#YSAwU{Uc|hnLBGQb~qzED! z1sg01B1%(1S6L820jU;1DJuK@y_u|>z2^*{ng7h4ejm7JuU{;D=SpGEcjbImIy|ow zcARS1G01Ti7IB=^`ieTv>Sm7Pk87|PZpTu%8_VE6ERDxd?ap9zyoOcKuephbVkOFr zFaWz@pyPO)p*E0?#i^KuWpN4Wg_qG6-$4z00E^;rRQpq?fzP2PbQ3G$pSE1Sg^7ov z+Bd}Z*a|B%zBA36VWk*N#hVy`KVWMNXldd-FpBbYRD3sTz*|@X|3W|XYvnlQFaS$p zE!2Xdu^P5VO=KwgGrlv53_I>jvK7ywI-Y~-XeDaPwqa#_4}}8p>MkX>VgI%Au>`b|3D&6?MtL?i!d)1O7cm@*wKW4Zw6?W&L+#iA zRDY@18MBeoboOB+-m~>}+j-24V%j-Qc`90<&Z-A$M}}h+Ohc_`K57N)P?vHuYUMkv z@1O=ggsMM*x}0C5`nzn)cThX)C`@1@tr|qZi{)9S*ZjLcO>gwe@eH2Koxs{v2w@ZliX< zuY)O^K%NOWAE3_gOVkcrvgJFd4tzTrtD;sC zh3c>iYGVCRmvSts{an<-)}h+HWy^=q`~9CIqpkSE8(`2*=7liSirb?;r#RHg;!zzZ zqwdNS)cXrjE8U1X>;0%5I)<9S1ys8SsD%Y^GyS-Kj*E;|To0>bE7VyJL`@(GHDD_G z;B?f$v#l>+IOVO@Q>dMIgvBtZit!CBPAuiEmTs2wW)q={EWy%&kvnU1LbhNC8u^(5!77iJUCz^hQ@ZK$(-5B0*A z7=c$%0|v#Jc44S?Ev&s!mu@ubE6BF-<<{*s{vj5p{%jok?@Q(~0qw*s48y;$Cf0e1 z-*1e=NKCWkJXHISP!sq9wbHAoBYK27bDyqerPa}oas=vbJYntVA)^)bMs2CvR-~f_ zm~QKrqVB*(tcH6~J97%Pb3fVo`?g%Vo0(8BYO5Qg-tUO|I{Kr=^`w*0jAo-6=As7P zgeCAz)XMguUO0uN@G@$PZ(~s`^Ry|KN9|}7a{rvB_ykTv-Kn=xzbnVEfxiD6WJ(gK z+}-@p)W&eiaabF(usNCoy|Zjf{Cat9*LUiEcD0CsCI8z_o2Rq!>G^p66&rL>1lQ>40S|pQ1L#f2~9#x ze70nKCzp&GZa{Uo2es8-p|-9yxb z{QHW*4)pFW(wwq_IoU9!a(ftyere_WJ%YlgZzolyM@jc5P0#i=%sgZiafj{5#zLk;{n>T9@wTKNrBN5!5o1BajnsD)a2 z40?Cg#-FwhMD;fkHPJ~PGMdRu)QXp3Ib4J4XeVmRKR^v|6a(-ZTYnA9Q+|MY&%dwf zrzQqc4o6M2EowsjQAeDFYVVmrMh&00ZnG61pk6$QHSi~_fF=5w3DrOi9F2;1LbV@^ z`f9Q;2v=f7+=05J2W|WU@}9@}gG@y#%JerAse?Mp7N|?t7As*V)Fm2}cjz#x{Z-Ua{*K!Eq66tq-+v`#FbwNpGt_{?P%n-~O=O}q2Q|T^ zsGZt?4RH%tjvIe*RgAOT&b(^v(sqJH~*hB!_W ztdEg69QD~OMGde9wWSA9m+nifkH4WNSbeDZ>FtQBPeF}0A9Y984rTvUv7Ue~&jDL; z1hwTSP-lAv_2LCv{teZ^ebjr8P+yVnF!Nqj)Wqtd7W4#aCwrk5ItsP(&w0q`k}O6Y z!EV&8-iKPjVT{I`sNeG7;oiSCozAHDGO!Z9fZEd6u_=Cv)v!>4+45l2ooRr<*akz; zGlYx=d=9m>b5ISJSvR6~;w{tw2T&6|gQ0i@HBiYBrk^m>PQ}=AthFoZr#K$9WBrkS zJkD@38elBya!p2U<#N_rWH99!ZU495zI<|~Lr9aR_9)_1q%A*hKbq57MK z>TenPGrqHhOd|q&P`C3MR>ga$hUJsYWvq`+Q0|F}&qcM%Lr%we7d7$9ZZlv6>O1d( zTHrv`*(YOloT@V8J8NuU7wS?RL(TN8^%iP^PO@2PW7I^OqbAe^HIZqkuVO8#pF`;V zs!;u1#aj3fbtzpV+5a|VT9VNnn1WH5gWAG;)DOi+*5jywzd>KTj2-b7YJf(g>`ygn zLPJp#oPs*KH&7GXkJ`Z#qwM$pBLNL~7uB&(iW#UB>cuLkiPXhn*bqapIch~iuqe(% zO>iD+WAk#s=eQMvjf#pI~8rqol!eG0L$VStb@;@ufG3HWPAwhz*4vy^};^X zS)W7=cnwS7FQ|z=L>+<81aoOCSzXpJ)B>VWcOxE?3vpM_pYpkM`qlS;lZ*zwjXFao z!>lL}_1hkX+S>N02@SIGY^+atDQd;yoYX39#zz3*H^;DLbcrtdN>_Ppda}c}YMQnzV+3bHy zG9$D3y~a(b%lI3r<3DZLccNKA0BY-NqjoA1HL>=n_KE1d-B^?*dR~?K|0& zE256NChEN?kF98F?TqTE7iy(rZ9E&b^)pZ_S%yKl8}*BL7bMso4AaRe=os3p69QEQzR7Vp~TQtqqFF;+=<*13SLUsI#E$>Bb`Nya&|J=sE zNA-UT)&4GOfxgcv&iPj-qZezTei))q1NTMU+7YOg%&_IvSex=*TRw*_%73Chx9Zc( z5yhb1?}asSJn9!Q7j=XmqA%k+7szNuS5Yhf!|Fq&1`fdT7=~I|Yjok$wmuDYM6*#V zTaDFlmyI7qz4slK#alN12t8E@l$&k_h`>seV^ISS!s?ig!MF@n|2k@bBdCEdp$5K( z6|u|=Q?7&RrxmJwAJnBDk6P&D8SKA0m_tBYx5oM!YGNOv20n{=;UTI+znSJM39+_B zeI5N!cO==CQ&B6Qin;>}QAfK1wWBZ1WDE2{J^@{hcTp2LicxqTb!PrK=7rX%74$}R zJP5V&F{la7Kuu%`YGHY(es`n#{Rp*yudO#dWVFRTv&@S%Q60vh&Z-M)ru}VvEULpC ztcxpAN3svq{%h34uAtieVJ$w}OsFcVeI!PrrwbXqm~9)(MGd$Xb!M-lR(8zRe~Vh- z4b)aXL=EIS$9%q(QGdCFqS}RHO>BXoI0Uue8ORQMoR`UHB_~l6`2#gWzq#fP)Ifcn ztxyx|f*NozYAe%Fmu@!dy*$(o?m_i;47K&=tiPh#6`dzJ|6np91e&5&+z-`ZCaR-F zsI%ONdhu=4z$Z}eUBY@;biVn^CkoX*4!sjaO&|@`?@ZKtYq2upJMWRvR-Hyo;0G*< z&H_^|hJF-7u?;rB#yAGGrR%XW9P z)DKaqMP{q3pe7Q9C9w(WacsPV2XV*iVi zDZJRcSPJVeA$)cIbVqf#0BZ?g46o#g~``1feDvirS&3s3RMM8aN%b zLkpI$|GHG`Y{hQWnH@pR_zaf82iOUVFEwY~12v(^*brBt2KWp$@C7V^cd;%y%ghml zVKIuasQNe$8Fdto+M0CK1XiLyZbeOSFX~bqLk(~qTjFi>!KmftYiW$?rw3|6=@^6a zQAhMX>QW!VvgkQOrZk!BsH3=VEwaMAP#Ob>S3pfD42xhh)CyXm2I^wVL$NmH6dPZP zF3MZ56n=tQ&}pPUk8_ob&h$QNW)*VHiW;D9bqs2zT~PxLMxFIY)X`+v`kAN+=2|zQ z+UMKye$+xwpcZr%%j)~TMMeWZvJHZsH@7wvHLiDEB zUqCIO@JjPuHPl~bjj$H>M(_7OkxY34%Tc#_3u>SvsL$s->P#P^et?2sFcWKv`by$4 z2%kg!t(l7&XcuY%$5Eg071ZZ>6V=aM^u&`Xv&#HWs)?vel8hB_oGs5mZRJwb7Oq7N zxDC~AKk6vHLhZ#2t>3wZ^VdM{6DWsAP@mI(umgUN+L2mo z%_WRLO|*@*8>+*Bw%%>y*{BK3LiL}Ey0q)DBJQ;HhdefL3ZtmFjxktuo%!W@8ug1f z0h{1ztcNGC0p3GxY3&!ynKwZlNjz$xG1eT^&aK0acnF)Lr+A*3X$RBL|`4JL7RKlhGF6Ms@Hf z>czq@o7-F(^+FhG3mc*)*ao%5-BD*h1hv)Uuqr-_8hAD8CwDzI#I4pd7_INW^m=ni z+F&pZ##xtOCCaa1YdnnlZhbbG3DiT~ndVp>yIDu0COi+duoYMZ*P(V`4{G9{VFcql z-;&V^d^eiEbSk5MLYt#H8itiI1@&SMdOJq_6t6?A{AJVtJ5f7$0QGepMeW#G)P!%K z+C4&#UJTr1Zo3P^C?}x?UW_`DRj7gUP#y0?wcCs8=zG-f!5wRf&E`KgU8sTkqjq4F zH3w@^erYrNuazDopsl}(HPP=CzF7=Mb-V&qzYTS(KS6bT9(CJ)#zt6di`l6bSetS@ z>isFGcFQmnw_;m7x`qAMN=t4vTNs30Dc3}uX&UNtT!iXqKdPhCsJn3;b=iEjnWJ-I zGs^8yzY~*jFwR9C@sFtSen)*Zj%T|a7*$addtw9BfX|@@$VKhI>-YqoKtC+{syUi6 zs2!g+F~Ch{xl#nL;>1S_NN zK$Nuu>b-ub2_|7R9E)1e0@Q@oV@ZAg+sK3wcnftq&tVJv1?yw@PIG2`Q4^Vh9dJIn z@Hp1NTd3O}^qN^|9n=;##u6BZ+M#%B5(eq}&m^NWU4YuM4XBRtQ4>03tStr(5RF;?II-(+;Q?O*3F3>=QSe3!5W7TIln zvumSn?`YIOOE3c8!A!i2&)}dp%uc<5I)VeJqx%$fX)mD`_BVQplL>g!T)wKP8P-NM z?26i%B-9VkRMZ)+#B#U;wZ;2U6FP-j`Cq7k{qoI30#Ki=3)NpU^uZqa?7y}qo`5Ej zU>lA>b&!MF!bR5SF`V*?)+1Pm@+kiJ5`V|>YG0W9LDCbe2KLe5&r{}y&+$;-v@rXx zEv-wzLj^nHeOlO;e;`(b$T|F&)S1+mx_2;%G@i7MdQEm5c|9{pUy$!jyNNc|m)JbY zx~QobXydwn-sdSBI8UiR`AixO#8$RmgitO@1HaH^AlpNXrud6 z9M{^m(d2cR3uCw0ROTC^avABKjiV1oD ze4E${wwywDdJP{q|QH^!Y%u94CPg%K9s|7AdLcX3g!1m-;vKI%^-dQ*Vy+8 zV+Ue-7LcZsuR-j(H^PsSZO31H-lrwy2-Z%c#3YIzXTubz8~5PC8BMNclbD4Q=Cp5qpVLlk&Ux3TaOv#{ZT;O&b4>cSz5X zwvzM&6aNvvr}4|Ul=2bsL8MP9kEX0=1gQzB2<2wfH6iI)Xic+rw(SQ~&Z0h9KRO?f zKBUmvR_eupq@PJWh}FUsqyXA&wr$2yu1bC*u?(z7nntQmT+dk2Na|LR^fa`-Wr|K) zTlVgMf-R_S6Zv0kqkH%jb z29~0(E#ASW3-SHkrs5o_B!LQ4_>=Zg)-##B9)IczoCTy_xXT7ixAh7 zW^fi!cGK>bEhmznL2NVSBvLioehhv{dPwY9e2e5c%O854CnfV@9G)QdB+1v-b;17B zB~#Z1`{A#ocSvg}>-mV(!?v$Q>^k}5#8OC2N&lgIo3w&7*1q#sA^t*~L_p7QoI{#M zT1SIww$W1RUd-JP6X=;sB*8|%rqvVVKSlkMWDfa1$j`zal+($-M7{+1r$`R@wWM9d zuafla!^On(JVX9VlD>!T#DYjaQNBdd(@p z=Ld`i+X?8ILVh*+QFdW4NzVXM3ER<4@;%AlRfT=N!r>(SOT4S?i>&wg$dWBuW83DDDjt6tQ0_%~nKYMFnZ{A5C)av0w?^YY&thBJMa^Q;2FguI)ySVEognGi zL3)FD4R48m^wHyUl#h}0tf2f1DaRC@^2B?SW)qu88b+E*-3!EKlGigq2Umf>Zc=9x zbIz0hjeK4F2}hAGkS|Kov%tEB{BiQ3bmqbiHm)|=#D2BqH?b|TVWbe!1Ipbo1`q2$ zo?j!gnpBFHE>m%ad=5#^Xi^IC7fGkdk0DjhbvKFf#1VO(G>Dcj5!G{m{9e)$;!{k> z$tCYgsz`h_>Ul^$lKgg4bP}&nKmMftU%4mc_igN|_veDv^$4`a?@3c_ zI$-XkF`HYK9-6AHQ*UTzKs4OW)_8! z_`WyH-(56(K)w;NDwtwBIYNF2v4yr>2>AshJ?%(7iM_rNBL%? zMI_H9{RJ+* zg)gYC{fKc{nXb6BY)tlnmGZS|(%=aJxn%WYE^Wo?mfXrhj0DtKY%Rqtdg|M!H6&q)o|6KKMa? z&b0G|h7^P|B4SzTsC0MA2pT#6gzZ|xrbNa@x;j16yWZn?|o*Y$I!XXYnN zZ&s*mL0>L+T4u64JuUa=nL+tGW<(Y)J|ZD2AuBmEJt6;8&X0xs3L56`nEh_GS_LK7 zl&o}DPbQWz!kz9;9F?X;Ok_2w?zH^GjTOs;x?M>rZkGJNHxg1aT(JomDH#z3q1=q! G_x=lR+pY@$ diff --git a/InvenTree/locale/de/LC_MESSAGES/django.po b/InvenTree/locale/de/LC_MESSAGES/django.po index 00c43092f0..242061af16 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-11-09 12:47+0000\n" +"POT-Creation-Date: 2020-11-10 13:31+0000\n" "PO-Revision-Date: 2020-05-03 11:32+0200\n" "Last-Translator: Christian Schlüter \n" "Language-Team: C \n" @@ -25,27 +25,27 @@ msgstr "Keine Aktion angegeben" msgid "No matching action found" msgstr "Keine passende Aktion gefunden" -#: InvenTree/forms.py:130 build/forms.py:82 build/forms.py:170 +#: InvenTree/forms.py:107 build/forms.py:82 build/forms.py:170 msgid "Confirm" msgstr "Bestätigen" -#: InvenTree/forms.py:146 +#: InvenTree/forms.py:123 #, fuzzy #| msgid "Confim BOM item deletion" msgid "Confirm item deletion" msgstr "Löschung von BOM-Position bestätigen" -#: InvenTree/forms.py:178 +#: InvenTree/forms.py:155 #, fuzzy #| msgid "Create new part" msgid "Enter new password" msgstr "Neues Teil anlegen" -#: InvenTree/forms.py:185 +#: InvenTree/forms.py:162 msgid "Confirm new password" msgstr "" -#: InvenTree/forms.py:220 +#: InvenTree/forms.py:197 msgid "Apply Theme" msgstr "" @@ -107,19 +107,19 @@ msgstr "Name" msgid "Description (optional)" msgstr "Firmenbeschreibung" -#: InvenTree/settings.py:348 +#: InvenTree/settings.py:350 msgid "English" msgstr "Englisch" -#: InvenTree/settings.py:349 +#: InvenTree/settings.py:351 msgid "German" msgstr "Deutsch" -#: InvenTree/settings.py:350 +#: InvenTree/settings.py:352 msgid "French" msgstr "Französisch" -#: InvenTree/settings.py:351 +#: InvenTree/settings.py:353 msgid "Polish" msgstr "Polnisch" @@ -306,7 +306,7 @@ msgstr "Bestell-Referenz" #: order/templates/order/sales_order_detail.html:156 #: part/templates/part/allocation.html:16 #: part/templates/part/allocation.html:49 -#: part/templates/part/sale_prices.html:80 stock/forms.py:297 +#: part/templates/part/sale_prices.html:82 stock/forms.py:298 #: stock/templates/stock/item_base.html:40 #: stock/templates/stock/item_base.html:46 #: stock/templates/stock/item_base.html:197 @@ -391,14 +391,13 @@ msgstr "Lagerobjekt für Zuordnung auswählen" #: build/models.py:56 build/templates/build/build_base.html:8 #: build/templates/build/build_base.html:35 #: part/templates/part/allocation.html:20 -#: stock/templates/stock/item_base.html:227 msgid "Build Order" msgstr "Bauauftrag" #: build/models.py:57 build/templates/build/index.html:6 #: build/templates/build/index.html:14 order/templates/order/so_builds.html:11 #: order/templates/order/so_tabs.html:9 part/templates/part/tabs.html:31 -#: templates/InvenTree/settings/tabs.html:28 users/models.py:30 +#: templates/InvenTree/settings/tabs.html:25 users/models.py:30 msgid "Build Orders" msgstr "Bauaufträge" @@ -520,7 +519,7 @@ msgstr "Bau-Status" msgid "Build status code" msgstr "Bau-Statuscode" -#: build/models.py:157 stock/models.py:389 +#: build/models.py:157 stock/models.py:390 msgid "Batch Code" msgstr "Losnummer" @@ -532,11 +531,11 @@ msgstr "Chargennummer für diese Bau-Ausgabe" #: company/templates/company/supplier_part_base.html:68 #: company/templates/company/supplier_part_detail.html:24 #: part/templates/part/detail.html:80 part/templates/part/part_base.html:102 -#: stock/models.py:383 stock/templates/stock/item_base.html:279 +#: stock/models.py:384 stock/templates/stock/item_base.html:280 msgid "External Link" msgstr "Externer Link" -#: build/models.py:177 part/models.py:597 stock/models.py:385 +#: build/models.py:177 part/models.py:609 stock/models.py:386 msgid "Link to external URL" msgstr "Link zu einer externen URL" @@ -544,8 +543,8 @@ msgstr "Link zu einer externen URL" #: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:18 #: order/templates/order/purchase_order_detail.html:203 #: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:73 -#: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:455 -#: stock/models.py:1428 stock/templates/stock/tabs.html:26 +#: stock/forms.py:307 stock/forms.py:339 stock/forms.py:367 stock/models.py:448 +#: stock/models.py:1432 stock/templates/stock/tabs.html:26 #: templates/js/barcode.js:391 templates/js/bom.js:250 #: templates/js/stock.js:116 templates/js/stock.js:578 msgid "Notes" @@ -620,11 +619,11 @@ msgstr "Bau starten um Teile zuzuweisen" msgid "Source stock item" msgstr "Bestand entfernen" -#: build/models.py:976 +#: build/models.py:975 msgid "Stock quantity to allocate to build" msgstr "Lagerobjekt-Anzahl dem Bau zuweisen" -#: build/models.py:984 +#: build/models.py:983 #, fuzzy #| msgid "Destination stock location" msgid "Destination stock item" @@ -709,8 +708,8 @@ msgid "" "The following stock items will be allocated to the specified build output" msgstr "Lagerobjekt dem Bau zuweisen" -#: build/templates/build/auto_allocate.html:18 stock/forms.py:336 -#: stock/templates/stock/item_base.html:233 +#: build/templates/build/auto_allocate.html:18 stock/forms.py:337 +#: stock/templates/stock/item_base.html:227 #: stock/templates/stock/stock_adjust.html:17 #: templates/InvenTree/search.html:183 templates/js/barcode.js:337 #: templates/js/build.js:418 templates/js/stock.js:570 @@ -778,7 +777,7 @@ msgstr "Bau-Status" #: build/templates/build/build_base.html:83 #: build/templates/build/detail.html:57 #: order/templates/order/receive_parts.html:24 -#: stock/templates/stock/item_base.html:311 templates/InvenTree/search.html:175 +#: stock/templates/stock/item_base.html:312 templates/InvenTree/search.html:175 #: templates/js/barcode.js:42 templates/js/build.js:675 #: templates/js/order.js:172 templates/js/order.js:254 #: templates/js/stock.js:557 templates/js/stock.js:961 @@ -918,7 +917,7 @@ msgstr "Lagerobjekt" msgid "Stock can be taken from any available location." msgstr "Bestand kann jedem verfügbaren Lagerort entnommen werden." -#: build/templates/build/detail.html:44 stock/forms.py:364 +#: build/templates/build/detail.html:44 stock/forms.py:365 #, fuzzy #| msgid "Description" msgid "Destination" @@ -931,7 +930,7 @@ msgid "Destination location not specified" msgstr "Hat dieses Teil Tracking für einzelne Objekte?" #: build/templates/build/detail.html:68 -#: stock/templates/stock/item_base.html:251 templates/js/stock.js:565 +#: stock/templates/stock/item_base.html:245 templates/js/stock.js:565 #: templates/js/stock.js:968 templates/js/table_filters.js:80 #: templates/js/table_filters.js:151 msgid "Batch" @@ -1036,7 +1035,7 @@ msgstr "Lagerbestand dem Bau zuweisen" msgid "Create Build Output" msgstr "Bau-Ausgabe" -#: build/views.py:207 stock/models.py:832 stock/views.py:1647 +#: build/views.py:207 stock/models.py:827 stock/views.py:1647 #, fuzzy #| msgid "Serial numbers already exist: " msgid "Serial numbers already exist" @@ -1175,7 +1174,7 @@ msgstr "Bauobjekt aktualisiert" msgid "Add Build Order Attachment" msgstr "Auftragsanhang hinzufügen" -#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:164 +#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:166 #: stock/views.py:176 msgid "Added attachment" msgstr "Anhang hinzugefügt" @@ -1196,188 +1195,201 @@ msgstr "Anhang löschen" msgid "Deleted attachment" msgstr "Anhang gelöscht" -#: common/models.py:51 +#: common/models.py:55 #, fuzzy #| msgid "Instance Name" msgid "InvenTree Instance Name" msgstr "Instanzname" -#: common/models.py:53 +#: common/models.py:57 #, fuzzy #| msgid "Brief description of the build" msgid "String descriptor for the server instance" msgstr "Kurze Beschreibung des Baus" -#: common/models.py:57 company/models.py:89 company/models.py:90 +#: common/models.py:61 company/models.py:89 company/models.py:90 msgid "Company name" msgstr "Firmenname" -#: common/models.py:58 +#: common/models.py:62 #, fuzzy #| msgid "Company name" msgid "Internal company name" msgstr "Firmenname" -#: common/models.py:63 +#: common/models.py:67 +#, fuzzy +#| msgid "Delete Currency" +msgid "Default Currency" +msgstr "Währung entfernen" + +#: common/models.py:68 +#, fuzzy +#| msgid "Delete Currency" +msgid "Default currency" +msgstr "Währung entfernen" + +#: common/models.py:74 msgid "IPN Regex" msgstr "" -#: common/models.py:64 +#: common/models.py:75 msgid "Regular expression pattern for matching Part IPN" msgstr "" -#: common/models.py:68 +#: common/models.py:79 +#, fuzzy +#| msgid "Duplicate Part" +msgid "Allow Duplicate IPN" +msgstr "Teil duplizieren" + +#: common/models.py:80 +msgid "Allow multiple parts to share the same IPN" +msgstr "" + +#: common/models.py:86 #, fuzzy #| msgid "Import BOM data" msgid "Copy Part BOM Data" msgstr "Stückliste importieren" -#: common/models.py:69 +#: common/models.py:87 msgid "Copy BOM data by default when duplicating a part" msgstr "" -#: common/models.py:75 +#: common/models.py:93 #, fuzzy #| msgid "Parameters" msgid "Copy Part Parameter Data" msgstr "Parameter" -#: common/models.py:76 +#: common/models.py:94 msgid "Copy parameter data by default when duplicating a part" msgstr "" -#: common/models.py:82 +#: common/models.py:100 #, fuzzy #| msgid "Parameters" msgid "Copy Part Test Data" msgstr "Parameter" -#: common/models.py:83 +#: common/models.py:101 msgid "Copy test data by default when duplicating a part" msgstr "" -#: common/models.py:89 part/models.py:668 part/templates/part/detail.html:168 +#: common/models.py:107 part/models.py:680 part/templates/part/detail.html:168 #: templates/js/table_filters.js:264 msgid "Component" msgstr "Komponente" -#: common/models.py:90 +#: common/models.py:108 #, fuzzy #| msgid "Part can be used in assemblies" msgid "Parts can be used as sub-components by default" msgstr "Teil kann in Baugruppen benutzt werden" -#: common/models.py:96 part/models.py:679 part/templates/part/detail.html:188 +#: common/models.py:114 part/models.py:691 part/templates/part/detail.html:188 msgid "Purchaseable" msgstr "Kaufbar" -#: common/models.py:97 +#: common/models.py:115 msgid "Parts are purchaseable by default" msgstr "" -#: common/models.py:103 part/models.py:684 part/templates/part/detail.html:198 +#: common/models.py:121 part/models.py:696 part/templates/part/detail.html:198 #: templates/js/table_filters.js:272 msgid "Salable" msgstr "Verkäuflich" -#: common/models.py:104 +#: common/models.py:122 msgid "Parts are salable by default" msgstr "" -#: common/models.py:110 part/models.py:674 part/templates/part/detail.html:178 +#: common/models.py:128 part/models.py:686 part/templates/part/detail.html:178 #: templates/js/table_filters.js:31 templates/js/table_filters.js:276 msgid "Trackable" msgstr "nachverfolgbar" -#: common/models.py:111 +#: common/models.py:129 msgid "Parts are trackable by default" msgstr "" -#: common/models.py:117 +#: common/models.py:135 #, fuzzy #| msgid "Order Reference" msgid "Build Order Reference Prefix" msgstr "Bestellreferenz" -#: common/models.py:118 +#: common/models.py:136 #, fuzzy #| msgid "Order reference" msgid "Prefix value for build order reference" msgstr "Bestell-Referenz" -#: common/models.py:123 +#: common/models.py:141 #, fuzzy #| msgid "Order Reference" msgid "Build Order Reference Regex" msgstr "Bestellreferenz" -#: common/models.py:124 +#: common/models.py:142 msgid "Regular expression pattern for matching build order reference" msgstr "" -#: common/models.py:128 +#: common/models.py:146 #, fuzzy #| msgid "Sales Order Reference" msgid "Sales Order Reference Prefix" msgstr "Bestellungsreferenz" -#: common/models.py:129 +#: common/models.py:147 #, fuzzy #| msgid "Order reference" msgid "Prefix value for sales order reference" msgstr "Bestell-Referenz" -#: common/models.py:133 +#: common/models.py:151 #, fuzzy #| msgid "Order reference" msgid "Purchase Order Reference Prefix" msgstr "Bestell-Referenz" -#: common/models.py:134 +#: common/models.py:152 #, fuzzy #| msgid "Order reference" msgid "Prefix value for purchase order reference" msgstr "Bestell-Referenz" -#: common/models.py:312 +#: common/models.py:357 msgid "Settings key (must be unique - case insensitive" msgstr "" "Einstellungs-Schlüssel (muss einzigartig sein, Groß-/ Kleinschreibung wird " "nicht beachtet)" -#: common/models.py:314 +#: common/models.py:359 msgid "Settings value" msgstr "Einstellungs-Wert" -#: common/models.py:366 +#: common/models.py:415 msgid "Value must be a boolean value" msgstr "" -#: common/models.py:380 +#: common/models.py:429 msgid "Key string must be unique" msgstr "Schlüsseltext muss eindeutig sein" -#: common/models.py:419 -msgid "Currency Symbol e.g. $" -msgstr "Währungs-Symbol (z.B. €)" +#: common/models.py:474 company/templates/company/supplier_part_pricing.html:80 +#: part/templates/part/sale_prices.html:87 templates/js/bom.js:234 +msgid "Price" +msgstr "Preis" -#: common/models.py:421 -msgid "Currency Suffix e.g. AUD" -msgstr "Währungs-Suffix (z.B. EUR)" +#: common/models.py:475 +#, fuzzy +#| msgid "Enter a valid quantity" +msgid "Unit price at specified quantity" +msgstr "Bitte eine gültige Anzahl eingeben" -#: common/models.py:423 -msgid "Currency Description" -msgstr "Währungs-Beschreibung" - -#: common/models.py:425 -msgid "Currency Value" -msgstr "Währungs-Wert" - -#: common/models.py:427 -msgid "Use this currency as the base currency" -msgstr "Benutze diese Währung als Basis-Währung" - -#: common/models.py:510 +#: common/models.py:498 #, fuzzy #| msgid "Default Location" msgid "Default" @@ -1389,19 +1401,7 @@ msgstr "Standard-Lagerort" msgid "Current value" msgstr "Währungs-Wert" -#: common/views.py:23 -msgid "Create new Currency" -msgstr "Neues Währung hinzufügen" - -#: common/views.py:31 -msgid "Edit Currency" -msgstr "Währung bearbeiten" - -#: common/views.py:38 -msgid "Delete Currency" -msgstr "Währung entfernen" - -#: common/views.py:49 +#: common/views.py:25 #, fuzzy #| msgid "Settings" msgid "Change Setting" @@ -1476,7 +1476,7 @@ msgstr "Kaufen Sie Teile von dieser Firma?" msgid "Does this company manufacture parts?" msgstr "Produziert diese Firma Teile?" -#: company/models.py:283 stock/models.py:337 +#: company/models.py:283 stock/models.py:338 #: stock/templates/stock/item_base.html:177 msgid "Base Part" msgstr "Basisteil" @@ -1550,14 +1550,14 @@ msgstr "Hersteller" #: company/templates/company/supplier_part_detail.html:21 #: order/templates/order/order_base.html:79 #: order/templates/order/order_wizard/select_pos.html:30 part/bom.py:170 -#: stock/templates/stock/item_base.html:286 templates/js/company.js:48 +#: stock/templates/stock/item_base.html:287 templates/js/company.js:48 #: templates/js/company.js:164 templates/js/order.js:154 msgid "Supplier" msgstr "Zulieferer" #: company/templates/company/detail.html:26 -#: order/templates/order/sales_order_base.html:81 stock/models.py:372 -#: stock/models.py:373 stock/templates/stock/item_base.html:204 +#: order/templates/order/sales_order_base.html:81 stock/models.py:373 +#: stock/models.py:374 stock/templates/stock/item_base.html:204 #: templates/js/company.js:40 templates/js/order.js:236 msgid "Customer" msgstr "Kunde" @@ -1609,21 +1609,21 @@ msgstr "Neues Teil" msgid "Create new Part" msgstr "Neues Teil hinzufügen" -#: company/templates/company/detail_part.html:69 company/views.py:53 +#: company/templates/company/detail_part.html:69 company/views.py:55 #: part/templates/part/supplier.html:47 msgid "New Supplier" msgstr "Neuer Zulieferer" -#: company/templates/company/detail_part.html:70 company/views.py:192 +#: company/templates/company/detail_part.html:70 company/views.py:194 msgid "Create new Supplier" msgstr "Neuen Zulieferer anlegen" -#: company/templates/company/detail_part.html:75 company/views.py:60 +#: company/templates/company/detail_part.html:75 company/views.py:62 #: part/templates/part/supplier.html:53 msgid "New Manufacturer" msgstr "Neuer Hersteller" -#: company/templates/company/detail_part.html:76 company/views.py:195 +#: company/templates/company/detail_part.html:76 company/views.py:197 msgid "Create new Manufacturer" msgstr "Neuen Hersteller anlegen" @@ -1658,7 +1658,7 @@ msgstr "" #: order/templates/order/purchase_orders.html:7 #: order/templates/order/purchase_orders.html:12 #: part/templates/part/orders.html:9 part/templates/part/tabs.html:48 -#: templates/InvenTree/settings/tabs.html:31 templates/navbar.html:33 +#: templates/InvenTree/settings/tabs.html:28 templates/navbar.html:33 #: users/models.py:31 msgid "Purchase Orders" msgstr "Bestellungen" @@ -1678,7 +1678,7 @@ msgstr "Neue Bestellung" #: order/templates/order/sales_orders.html:7 #: order/templates/order/sales_orders.html:12 #: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:56 -#: templates/InvenTree/settings/tabs.html:34 templates/navbar.html:42 +#: templates/InvenTree/settings/tabs.html:31 templates/navbar.html:42 #: users/models.py:32 msgid "Sales Orders" msgstr "Bestellungen" @@ -1694,8 +1694,8 @@ msgid "New Sales Order" msgstr "Neuer Auftrag" #: company/templates/company/supplier_part_base.html:6 -#: company/templates/company/supplier_part_base.html:19 stock/models.py:346 -#: stock/templates/stock/item_base.html:291 templates/js/company.js:180 +#: company/templates/company/supplier_part_base.html:19 stock/models.py:347 +#: stock/templates/stock/item_base.html:292 templates/js/company.js:180 msgid "Supplier Part" msgstr "Zulieferer-Teil" @@ -1751,32 +1751,27 @@ msgstr "Teil bestellen" msgid "Pricing Information" msgstr "Preisinformationen ansehen" -#: company/templates/company/supplier_part_pricing.html:17 company/views.py:410 -#: part/templates/part/sale_prices.html:13 part/views.py:2360 +#: company/templates/company/supplier_part_pricing.html:17 company/views.py:412 +#: part/templates/part/sale_prices.html:14 part/views.py:2350 msgid "Add Price Break" msgstr "Preisstaffel hinzufügen" #: company/templates/company/supplier_part_pricing.html:36 -#: part/templates/part/sale_prices.html:41 +#: part/templates/part/sale_prices.html:43 #, fuzzy #| msgid "No company information found" msgid "No price break information found" msgstr "Keine Firmeninformation gefunden" -#: company/templates/company/supplier_part_pricing.html:80 -#: part/templates/part/sale_prices.html:85 templates/js/bom.js:234 -msgid "Price" -msgstr "Preis" - -#: company/templates/company/supplier_part_pricing.html:94 -#: part/templates/part/sale_prices.html:99 +#: company/templates/company/supplier_part_pricing.html:87 +#: part/templates/part/sale_prices.html:94 #, fuzzy #| msgid "Edit Price Break" msgid "Edit price break" msgstr "Preisstaffel bearbeiten" -#: company/templates/company/supplier_part_pricing.html:95 -#: part/templates/part/sale_prices.html:100 +#: company/templates/company/supplier_part_pricing.html:88 +#: part/templates/part/sale_prices.html:95 #, fuzzy #| msgid "Delete Price Break" msgid "Delete price break" @@ -1793,7 +1788,7 @@ msgstr "Bepreisung" #: company/templates/company/supplier_part_tabs.html:8 #: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18 #: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155 -#: templates/InvenTree/settings/tabs.html:25 templates/js/part.js:192 +#: templates/InvenTree/settings/tabs.html:22 templates/js/part.js:192 #: templates/js/part.js:418 templates/js/stock.js:502 templates/navbar.html:22 #: users/models.py:29 msgid "Stock" @@ -1807,95 +1802,95 @@ msgstr "Bestellungen" #: order/templates/order/receive_parts.html:14 part/models.py:295 #: part/templates/part/cat_link.html:7 part/templates/part/category.html:94 #: part/templates/part/category_tabs.html:6 -#: templates/InvenTree/settings/tabs.html:22 templates/navbar.html:19 +#: templates/InvenTree/settings/tabs.html:19 templates/navbar.html:19 #: templates/stats.html:8 templates/stats.html:17 users/models.py:28 msgid "Parts" msgstr "Teile" -#: company/views.py:52 part/templates/part/tabs.html:42 +#: company/views.py:54 part/templates/part/tabs.html:42 #: templates/navbar.html:31 msgid "Suppliers" msgstr "Zulieferer" -#: company/views.py:59 templates/navbar.html:32 +#: company/views.py:61 templates/navbar.html:32 msgid "Manufacturers" msgstr "Hersteller" -#: company/views.py:66 templates/navbar.html:41 +#: company/views.py:68 templates/navbar.html:41 msgid "Customers" msgstr "Kunden" -#: company/views.py:67 +#: company/views.py:69 msgid "New Customer" msgstr "Neuer Kunde" -#: company/views.py:75 +#: company/views.py:77 msgid "Companies" msgstr "Firmen" -#: company/views.py:76 +#: company/views.py:78 msgid "New Company" msgstr "Neue Firma" -#: company/views.py:154 +#: company/views.py:156 msgid "Update Company Image" msgstr "Firmenbild aktualisieren" -#: company/views.py:160 +#: company/views.py:162 msgid "Updated company image" msgstr "Aktualisiertes Firmenbild" -#: company/views.py:170 +#: company/views.py:172 msgid "Edit Company" msgstr "Firma bearbeiten" -#: company/views.py:175 +#: company/views.py:177 msgid "Edited company information" msgstr "Firmeninformation bearbeitet" -#: company/views.py:198 +#: company/views.py:200 msgid "Create new Customer" msgstr "Neuen Kunden anlegen" -#: company/views.py:200 +#: company/views.py:202 msgid "Create new Company" msgstr "Neue Firma anlegen" -#: company/views.py:227 +#: company/views.py:229 msgid "Created new company" msgstr "Neue Firma angelegt" -#: company/views.py:237 +#: company/views.py:239 msgid "Delete Company" msgstr "Firma löschen" -#: company/views.py:243 +#: company/views.py:245 msgid "Company was deleted" msgstr "Firma gelöscht" -#: company/views.py:268 +#: company/views.py:270 msgid "Edit Supplier Part" msgstr "Zuliefererteil bearbeiten" -#: company/views.py:278 templates/js/stock.js:846 +#: company/views.py:280 templates/js/stock.js:846 msgid "Create new Supplier Part" msgstr "Neues Zuliefererteil anlegen" -#: company/views.py:339 +#: company/views.py:341 msgid "Delete Supplier Part" msgstr "Zuliefererteil entfernen" -#: company/views.py:416 part/views.py:2366 +#: company/views.py:418 part/views.py:2356 #, fuzzy #| msgid "Add Price Break" msgid "Added new price break" msgstr "Preisstaffel hinzufügen" -#: company/views.py:453 part/views.py:2411 +#: company/views.py:454 part/views.py:2400 msgid "Edit Price Break" msgstr "Preisstaffel bearbeiten" -#: company/views.py:469 part/views.py:2427 +#: company/views.py:470 part/views.py:2416 msgid "Delete Price Break" msgstr "Preisstaffel löschen" @@ -2000,8 +1995,8 @@ msgstr "" msgid "Date order was completed" msgstr "Bestellung als vollständig markieren" -#: order/models.py:185 order/models.py:267 part/views.py:1477 -#: stock/models.py:243 stock/models.py:816 +#: order/models.py:185 order/models.py:267 part/views.py:1479 +#: stock/models.py:244 stock/models.py:811 msgid "Quantity must be greater than zero" msgstr "Anzahl muss größer Null sein" @@ -2039,7 +2034,7 @@ msgstr "Position - Notizen" #: order/models.py:484 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 -#: stock/templates/stock/item_base.html:265 templates/js/order.js:139 +#: stock/templates/stock/item_base.html:259 templates/js/order.js:139 msgid "Purchase Order" msgstr "Kaufvertrag" @@ -2067,11 +2062,11 @@ msgstr "zugewiesene Anzahl darf nicht die verfügbare Anzahl überschreiten" msgid "Quantity must be 1 for serialized stock item" msgstr "Anzahl muss 1 für Objekte mit Seriennummer sein" -#: order/models.py:626 +#: order/models.py:625 msgid "Select stock item to allocate" msgstr "Lagerobjekt für Zuordnung auswählen" -#: order/models.py:629 +#: order/models.py:628 msgid "Enter stock allocation quantity" msgstr "Zuordnungsanzahl eingeben" @@ -2293,7 +2288,7 @@ msgid "Sales Order Items" msgstr "Auftragspositionen" #: order/templates/order/sales_order_detail.html:72 -#: order/templates/order/sales_order_detail.html:154 stock/models.py:377 +#: order/templates/order/sales_order_detail.html:154 stock/models.py:378 #: stock/templates/stock/item_base.html:191 templates/js/build.js:402 msgid "Serial Number" msgstr "Seriennummer" @@ -2542,103 +2537,103 @@ msgstr "Fehler beim Lesen der Stückliste (ungültige Daten)" msgid "Error reading BOM file (incorrect row size)" msgstr "Fehler beim Lesen der Stückliste (ungültige Zeilengröße)" -#: part/forms.py:62 stock/forms.py:254 +#: part/forms.py:60 stock/forms.py:255 msgid "File Format" msgstr "Dateiformat" -#: part/forms.py:62 stock/forms.py:254 +#: part/forms.py:60 stock/forms.py:255 msgid "Select output file format" msgstr "Ausgabe-Dateiformat auswählen" -#: part/forms.py:64 +#: part/forms.py:62 msgid "Cascading" msgstr "Kaskadierend" -#: part/forms.py:64 +#: part/forms.py:62 msgid "Download cascading / multi-level BOM" msgstr "Kaskadierende Stückliste herunterladen" -#: part/forms.py:66 +#: part/forms.py:64 msgid "Levels" msgstr "" -#: part/forms.py:66 +#: part/forms.py:64 msgid "Select maximum number of BOM levels to export (0 = all levels)" msgstr "" -#: part/forms.py:68 +#: part/forms.py:66 #, fuzzy #| msgid "New Parameter" msgid "Include Parameter Data" msgstr "Neuer Parameter" -#: part/forms.py:68 +#: part/forms.py:66 msgid "Include part parameters data in exported BOM" msgstr "" -#: part/forms.py:70 +#: part/forms.py:68 #, fuzzy #| msgid "Include stock in sublocations" msgid "Include Stock Data" msgstr "Bestand in Unterlagerorten einschließen" -#: part/forms.py:70 +#: part/forms.py:68 #, fuzzy #| msgid "Include parts in subcategories" msgid "Include part stock data in exported BOM" msgstr "Teile in Unterkategorien einschließen" -#: part/forms.py:72 +#: part/forms.py:70 #, fuzzy #| msgid "New Supplier Part" msgid "Include Supplier Data" msgstr "Neues Zulieferer-Teil" -#: part/forms.py:72 +#: part/forms.py:70 msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:93 part/models.py:1632 +#: part/forms.py:91 part/models.py:1644 msgid "Parent Part" msgstr "Ausgangsteil" -#: part/forms.py:94 part/templates/part/bom_duplicate.html:7 +#: part/forms.py:92 part/templates/part/bom_duplicate.html:7 #, fuzzy #| msgid "Select parent part" msgid "Select parent part to copy BOM from" msgstr "Ausgangsteil auswählen" -#: part/forms.py:100 +#: part/forms.py:98 #, fuzzy #| msgid "Select from existing images" msgid "Clear existing BOM items" msgstr "Aus vorhandenen Bildern auswählen" -#: part/forms.py:105 +#: part/forms.py:103 #, fuzzy #| msgid "Confim BOM item deletion" msgid "Confirm BOM duplication" msgstr "Löschung von BOM-Position bestätigen" -#: part/forms.py:123 +#: part/forms.py:121 msgid "Confirm that the BOM is correct" msgstr "Bestätigen, dass die Stückliste korrekt ist" -#: part/forms.py:135 +#: part/forms.py:133 msgid "Select BOM file to upload" msgstr "Stücklisten-Datei zum Upload auswählen" -#: part/forms.py:154 +#: part/forms.py:152 #, fuzzy #| msgid "Delete Parts" msgid "Related Part" msgstr "Teile löschen" -#: part/forms.py:173 +#: part/forms.py:171 msgid "Select part category" msgstr "Teilekategorie wählen" -#: part/forms.py:189 +#: part/forms.py:187 #, fuzzy #| msgid "Perform 'deep copy' which will duplicate all BOM data for this part" msgid "Duplicate all BOM data for this part" @@ -2646,32 +2641,28 @@ msgstr "" "Tiefe Kopie ausführen. Dies wird alle Daten der Stückliste für dieses Teil " "duplizieren" -#: part/forms.py:190 +#: part/forms.py:188 msgid "Copy BOM" msgstr "" -#: part/forms.py:195 +#: part/forms.py:193 msgid "Duplicate all parameter data for this part" msgstr "" -#: part/forms.py:196 +#: part/forms.py:194 #, fuzzy #| msgid "Parameters" msgid "Copy Parameters" msgstr "Parameter" -#: part/forms.py:201 +#: part/forms.py:199 msgid "Confirm part creation" msgstr "Erstellen des Teils bestätigen" -#: part/forms.py:298 +#: part/forms.py:296 msgid "Input quantity for price calculation" msgstr "Eintragsmenge zur Preisberechnung" -#: part/forms.py:301 -msgid "Select currency for price calculation" -msgstr "Währung zur Preisberechnung wählen" - #: part/models.py:67 msgid "Default location for parts in this category" msgstr "Standard-Standort für Teile dieser Kategorie" @@ -2710,117 +2701,121 @@ msgstr "" msgid "Most recent serial number is" msgstr "Keine Seriennummer angegeben" -#: part/models.py:540 +#: part/models.py:541 +msgid "Duplicate IPN not allowed in part settings" +msgstr "" + +#: part/models.py:552 msgid "Part must be unique for name, IPN and revision" msgstr "Namen, Teile- und Revisionsnummern müssen eindeutig sein" -#: part/models.py:569 part/templates/part/detail.html:19 +#: part/models.py:581 part/templates/part/detail.html:19 msgid "Part name" msgstr "Name des Teils" -#: part/models.py:573 +#: part/models.py:585 msgid "Is this part a template part?" msgstr "Ist dieses Teil eine Vorlage?" -#: part/models.py:582 +#: part/models.py:594 msgid "Is this part a variant of another part?" msgstr "Ist dieses Teil eine Variante eines anderen Teils?" -#: part/models.py:584 +#: part/models.py:596 msgid "Part description" msgstr "Beschreibung des Teils" -#: part/models.py:586 +#: part/models.py:598 msgid "Part keywords to improve visibility in search results" msgstr "Schlüsselworte um die Sichtbarkeit in Suchergebnissen zu verbessern" -#: part/models.py:591 +#: part/models.py:603 msgid "Part category" msgstr "Teile-Kategorie" -#: part/models.py:593 +#: part/models.py:605 msgid "Internal Part Number" msgstr "Interne Teilenummer" -#: part/models.py:595 +#: part/models.py:607 msgid "Part revision or version number" msgstr "Revisions- oder Versionsnummer" -#: part/models.py:609 +#: part/models.py:621 msgid "Where is this item normally stored?" msgstr "Wo wird dieses Teil normalerweise gelagert?" -#: part/models.py:653 +#: part/models.py:665 msgid "Default supplier part" msgstr "Standard-Zulieferer?" -#: part/models.py:656 +#: part/models.py:668 msgid "Minimum allowed stock level" msgstr "Minimal zulässiger Lagerbestand" -#: part/models.py:658 +#: part/models.py:670 msgid "Stock keeping units for this part" msgstr "Stock Keeping Units (SKU) für dieses Teil" -#: part/models.py:662 part/templates/part/detail.html:158 +#: part/models.py:674 part/templates/part/detail.html:158 #: templates/js/table_filters.js:260 msgid "Assembly" msgstr "Baugruppe" -#: part/models.py:663 +#: part/models.py:675 msgid "Can this part be built from other parts?" msgstr "Kann dieses Teil aus anderen Teilen angefertigt werden?" -#: part/models.py:669 +#: part/models.py:681 msgid "Can this part be used to build other parts?" msgstr "Kann dieses Teil zum Bau von anderen genutzt werden?" -#: part/models.py:675 +#: part/models.py:687 msgid "Does this part have tracking for unique items?" msgstr "Hat dieses Teil Tracking für einzelne Objekte?" -#: part/models.py:680 +#: part/models.py:692 msgid "Can this part be purchased from external suppliers?" msgstr "Kann dieses Teil von externen Zulieferern gekauft werden?" -#: part/models.py:685 +#: part/models.py:697 msgid "Can this part be sold to customers?" msgstr "Kann dieses Teil an Kunden verkauft werden?" -#: part/models.py:689 part/templates/part/detail.html:215 +#: part/models.py:701 part/templates/part/detail.html:215 #: templates/js/table_filters.js:19 templates/js/table_filters.js:55 #: templates/js/table_filters.js:186 templates/js/table_filters.js:243 msgid "Active" msgstr "Aktiv" -#: part/models.py:690 +#: part/models.py:702 msgid "Is this part active?" msgstr "Ist dieses Teil aktiv?" -#: part/models.py:694 part/templates/part/detail.html:138 +#: part/models.py:706 part/templates/part/detail.html:138 #: templates/js/table_filters.js:27 msgid "Virtual" msgstr "Virtuell" -#: part/models.py:695 +#: part/models.py:707 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:697 +#: part/models.py:709 msgid "Part notes - supports Markdown formatting" msgstr "Bemerkungen - unterstüzt Markdown-Formatierung" -#: part/models.py:699 +#: part/models.py:711 msgid "Stored BOM checksum" msgstr "Prüfsumme der Stückliste gespeichert" -#: part/models.py:1505 +#: part/models.py:1517 #, fuzzy #| msgid "Stock item cannot be created for a template Part" msgid "Test templates can only be created for trackable parts" msgstr "Lagerobjekt kann nicht für Vorlagen-Teile angelegt werden" -#: part/models.py:1522 +#: part/models.py:1534 #, fuzzy #| msgid "" #| "A stock item with this serial number already exists for template part " @@ -2830,133 +2825,133 @@ msgstr "" "Ein Teil mit dieser Seriennummer existiert bereits für die Teilevorlage " "{part}" -#: part/models.py:1541 templates/js/part.js:567 templates/js/stock.js:92 +#: part/models.py:1553 templates/js/part.js:567 templates/js/stock.js:92 #, fuzzy #| msgid "Instance Name" msgid "Test Name" msgstr "Instanzname" -#: part/models.py:1542 +#: part/models.py:1554 #, fuzzy #| msgid "Serial number for this item" msgid "Enter a name for the test" msgstr "Seriennummer für dieses Teil" -#: part/models.py:1547 +#: part/models.py:1559 #, fuzzy #| msgid "Description" msgid "Test Description" msgstr "Beschreibung" -#: part/models.py:1548 +#: part/models.py:1560 #, fuzzy #| msgid "Brief description of the build" msgid "Enter description for this test" msgstr "Kurze Beschreibung des Baus" -#: part/models.py:1553 templates/js/part.js:576 +#: part/models.py:1565 templates/js/part.js:576 #: templates/js/table_filters.js:172 msgid "Required" msgstr "benötigt" -#: part/models.py:1554 +#: part/models.py:1566 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1559 templates/js/part.js:584 +#: part/models.py:1571 templates/js/part.js:584 #, fuzzy #| msgid "Required Parts" msgid "Requires Value" msgstr "benötigte Teile" -#: part/models.py:1560 +#: part/models.py:1572 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1565 templates/js/part.js:591 +#: part/models.py:1577 templates/js/part.js:591 #, fuzzy #| msgid "Delete Attachment" msgid "Requires Attachment" msgstr "Anhang löschen" -#: part/models.py:1566 +#: part/models.py:1578 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1599 +#: part/models.py:1611 msgid "Parameter template name must be unique" msgstr "Vorlagen-Name des Parameters muss eindeutig sein" -#: part/models.py:1604 +#: part/models.py:1616 msgid "Parameter Name" msgstr "Name des Parameters" -#: part/models.py:1606 +#: part/models.py:1618 msgid "Parameter Units" msgstr "Parameter Einheit" -#: part/models.py:1634 +#: part/models.py:1646 msgid "Parameter Template" msgstr "Parameter Vorlage" -#: part/models.py:1636 +#: part/models.py:1648 msgid "Parameter Value" msgstr "Parameter Wert" -#: part/models.py:1673 +#: part/models.py:1685 msgid "Select parent part" msgstr "Ausgangsteil auswählen" -#: part/models.py:1681 +#: part/models.py:1693 msgid "Select part to be used in BOM" msgstr "Teil für die Nutzung in der Stückliste auswählen" -#: part/models.py:1687 +#: part/models.py:1699 msgid "BOM quantity for this BOM item" msgstr "Stücklisten-Anzahl für dieses Stücklisten-Teil" -#: part/models.py:1689 +#: part/models.py:1701 #, fuzzy #| msgid "Confim BOM item deletion" msgid "This BOM item is optional" msgstr "Löschung von BOM-Position bestätigen" -#: part/models.py:1692 +#: part/models.py:1704 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "Geschätzter Ausschuss (absolut oder prozentual)" -#: part/models.py:1695 +#: part/models.py:1707 msgid "BOM item reference" msgstr "Referenz des Objekts auf der Stückliste" -#: part/models.py:1698 +#: part/models.py:1710 msgid "BOM item notes" msgstr "Notizen zum Stücklisten-Objekt" -#: part/models.py:1700 +#: part/models.py:1712 msgid "BOM line checksum" msgstr "Prüfsumme der Stückliste" -#: part/models.py:1767 part/views.py:1483 part/views.py:1535 -#: stock/models.py:233 +#: part/models.py:1779 part/views.py:1485 part/views.py:1537 +#: stock/models.py:234 #, fuzzy #| msgid "Overage must be an integer value or a percentage" msgid "Quantity must be integer value for trackable parts" msgstr "Ãœberschuss muss eine Ganzzahl oder ein Prozentwert sein" -#: part/models.py:1783 +#: part/models.py:1795 #, fuzzy #| msgid "New BOM Item" msgid "BOM Item" msgstr "Neue Stücklistenposition" -#: part/models.py:1898 +#: part/models.py:1910 #, fuzzy #| msgid "Select a part" msgid "Select Related Part" msgstr "Teil auswählen" -#: part/models.py:1930 +#: part/models.py:1942 msgid "" "Error creating relationship: check that the part is not related to itself " "and that the relationship is unique" @@ -2977,7 +2972,7 @@ msgstr "Bestellung" #: part/templates/part/allocation.html:45 #: stock/templates/stock/item_base.html:8 #: stock/templates/stock/item_base.html:72 -#: stock/templates/stock/item_base.html:273 +#: stock/templates/stock/item_base.html:274 #: stock/templates/stock/stock_adjust.html:16 templates/js/build.js:724 #: templates/js/stock.js:695 templates/js/stock.js:944 msgid "Stock Item" @@ -3052,7 +3047,7 @@ msgstr "Stückliste validieren" msgid "Validate" msgstr "BOM validieren" -#: part/templates/part/bom.html:62 part/views.py:1774 +#: part/templates/part/bom.html:62 part/views.py:1776 msgid "Export Bill of Materials" msgstr "Stückliste exportieren" @@ -3174,7 +3169,7 @@ msgstr "Neuen Bau beginnen" msgid "All parts" msgstr "Alle Teile" -#: part/templates/part/category.html:24 part/views.py:2177 +#: part/templates/part/category.html:24 part/views.py:2167 msgid "Create new part category" msgstr "Teilkategorie anlegen" @@ -3356,7 +3351,7 @@ msgstr "Teil ist virtuell (kein physisches Teil)" msgid "Part is not a virtual part" msgstr "Teil ist nicht virtuell" -#: part/templates/part/detail.html:148 stock/forms.py:248 +#: part/templates/part/detail.html:148 stock/forms.py:249 #: templates/js/table_filters.js:23 templates/js/table_filters.js:248 msgid "Template" msgstr "Vorlage" @@ -3433,17 +3428,17 @@ msgstr "Teilparameter" msgid "Add new parameter" msgstr "Parameter hinzufügen" -#: part/templates/part/params.html:15 templates/InvenTree/settings/part.html:35 +#: part/templates/part/params.html:15 templates/InvenTree/settings/part.html:37 msgid "New Parameter" msgstr "Neuer Parameter" -#: part/templates/part/params.html:25 stock/models.py:1415 +#: part/templates/part/params.html:25 stock/models.py:1419 #: templates/js/stock.js:112 msgid "Value" msgstr "Wert" #: part/templates/part/params.html:41 part/templates/part/related.html:41 -#: part/templates/part/supplier.html:19 users/models.py:147 +#: part/templates/part/supplier.html:19 users/models.py:148 msgid "Delete" msgstr "Löschen" @@ -3657,7 +3652,7 @@ msgstr "Stückliste" msgid "Used In" msgstr "Benutzt in" -#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:317 +#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:318 msgid "Tests" msgstr "" @@ -3693,230 +3688,230 @@ msgstr "Neues Teil hinzufügen" msgid "New Variant" msgstr "Varianten" -#: part/views.py:80 +#: part/views.py:82 #, fuzzy #| msgid "Allocated Parts" msgid "Add Related Part" msgstr "Zugeordnete Teile" -#: part/views.py:136 +#: part/views.py:138 #, fuzzy #| msgid "Delete Supplier Part" msgid "Delete Related Part" msgstr "Zuliefererteil entfernen" -#: part/views.py:148 +#: part/views.py:150 msgid "Add part attachment" msgstr "Teilanhang hinzufügen" -#: part/views.py:203 templates/attachment_table.html:34 +#: part/views.py:205 templates/attachment_table.html:34 msgid "Edit attachment" msgstr "Anhang bearbeiten" -#: part/views.py:209 +#: part/views.py:211 msgid "Part attachment updated" msgstr "Teilanhang aktualisiert" -#: part/views.py:224 +#: part/views.py:226 msgid "Delete Part Attachment" msgstr "Teilanhang löschen" -#: part/views.py:232 +#: part/views.py:234 msgid "Deleted part attachment" msgstr "Teilanhang gelöscht" -#: part/views.py:241 +#: part/views.py:243 #, fuzzy #| msgid "Create Part Parameter Template" msgid "Create Test Template" msgstr "Teilparametervorlage anlegen" -#: part/views.py:270 +#: part/views.py:272 #, fuzzy #| msgid "Edit Template" msgid "Edit Test Template" msgstr "Vorlage bearbeiten" -#: part/views.py:286 +#: part/views.py:288 #, fuzzy #| msgid "Delete Template" msgid "Delete Test Template" msgstr "Vorlage löschen" -#: part/views.py:295 +#: part/views.py:297 msgid "Set Part Category" msgstr "Teilkategorie auswählen" -#: part/views.py:345 +#: part/views.py:347 #, python-brace-format msgid "Set category for {n} parts" msgstr "Kategorie für {n} Teile setzen" -#: part/views.py:380 +#: part/views.py:382 msgid "Create Variant" msgstr "Variante anlegen" -#: part/views.py:462 +#: part/views.py:464 msgid "Duplicate Part" msgstr "Teil duplizieren" -#: part/views.py:469 +#: part/views.py:471 msgid "Copied part" msgstr "Teil kopiert" -#: part/views.py:523 part/views.py:653 +#: part/views.py:525 part/views.py:655 msgid "Possible matches exist - confirm creation of new part" msgstr "" -#: part/views.py:588 templates/js/stock.js:840 +#: part/views.py:590 templates/js/stock.js:840 msgid "Create New Part" msgstr "Neues Teil anlegen" -#: part/views.py:595 +#: part/views.py:597 msgid "Created new part" msgstr "Neues Teil angelegt" -#: part/views.py:811 +#: part/views.py:813 msgid "Part QR Code" msgstr "Teil-QR-Code" -#: part/views.py:830 +#: part/views.py:832 msgid "Upload Part Image" msgstr "Teilbild hochladen" -#: part/views.py:838 part/views.py:875 +#: part/views.py:840 part/views.py:877 msgid "Updated part image" msgstr "Teilbild aktualisiert" -#: part/views.py:847 +#: part/views.py:849 msgid "Select Part Image" msgstr "Teilbild auswählen" -#: part/views.py:878 +#: part/views.py:880 msgid "Part image not found" msgstr "Teilbild nicht gefunden" -#: part/views.py:889 +#: part/views.py:891 msgid "Edit Part Properties" msgstr "Teileigenschaften bearbeiten" -#: part/views.py:916 +#: part/views.py:918 #, fuzzy #| msgid "Duplicate Part" msgid "Duplicate BOM" msgstr "Teil duplizieren" -#: part/views.py:947 +#: part/views.py:949 #, fuzzy #| msgid "Confirm unallocation of build stock" msgid "Confirm duplication of BOM from parent" msgstr "Zuweisungsaufhebung bestätigen" -#: part/views.py:968 +#: part/views.py:970 msgid "Validate BOM" msgstr "BOM validieren" -#: part/views.py:991 +#: part/views.py:993 #, fuzzy #| msgid "Confirm that the BOM is correct" msgid "Confirm that the BOM is valid" msgstr "Bestätigen, dass die Stückliste korrekt ist" -#: part/views.py:1002 +#: part/views.py:1004 #, fuzzy #| msgid "Validate Bill of Materials" msgid "Validated Bill of Materials" msgstr "Stückliste validieren" -#: part/views.py:1136 +#: part/views.py:1138 msgid "No BOM file provided" msgstr "Keine Stückliste angegeben" -#: part/views.py:1486 +#: part/views.py:1488 msgid "Enter a valid quantity" msgstr "Bitte eine gültige Anzahl eingeben" -#: part/views.py:1511 part/views.py:1514 +#: part/views.py:1513 part/views.py:1516 msgid "Select valid part" msgstr "Bitte ein gültiges Teil auswählen" -#: part/views.py:1520 +#: part/views.py:1522 msgid "Duplicate part selected" msgstr "Teil doppelt ausgewählt" -#: part/views.py:1558 +#: part/views.py:1560 msgid "Select a part" msgstr "Teil auswählen" -#: part/views.py:1564 +#: part/views.py:1566 #, fuzzy #| msgid "Select part to be used in BOM" msgid "Selected part creates a circular BOM" msgstr "Teil für die Nutzung in der Stückliste auswählen" -#: part/views.py:1568 +#: part/views.py:1570 msgid "Specify quantity" msgstr "Anzahl angeben" -#: part/views.py:1824 +#: part/views.py:1826 msgid "Confirm Part Deletion" msgstr "Löschen des Teils bestätigen" -#: part/views.py:1833 +#: part/views.py:1835 msgid "Part was deleted" msgstr "Teil wurde gelöscht" -#: part/views.py:1842 +#: part/views.py:1844 msgid "Part Pricing" msgstr "Teilbepreisung" -#: part/views.py:1968 +#: part/views.py:1958 msgid "Create Part Parameter Template" msgstr "Teilparametervorlage anlegen" -#: part/views.py:1978 +#: part/views.py:1968 msgid "Edit Part Parameter Template" msgstr "Teilparametervorlage bearbeiten" -#: part/views.py:1987 +#: part/views.py:1977 msgid "Delete Part Parameter Template" msgstr "Teilparametervorlage löschen" -#: part/views.py:1997 +#: part/views.py:1987 msgid "Create Part Parameter" msgstr "Teilparameter anlegen" -#: part/views.py:2049 +#: part/views.py:2039 msgid "Edit Part Parameter" msgstr "Teilparameter bearbeiten" -#: part/views.py:2065 +#: part/views.py:2055 msgid "Delete Part Parameter" msgstr "Teilparameter löschen" -#: part/views.py:2124 +#: part/views.py:2114 msgid "Edit Part Category" msgstr "Teilkategorie bearbeiten" -#: part/views.py:2161 +#: part/views.py:2151 msgid "Delete Part Category" msgstr "Teilkategorie löschen" -#: part/views.py:2169 +#: part/views.py:2159 msgid "Part category was deleted" msgstr "Teilekategorie wurde gelöscht" -#: part/views.py:2232 +#: part/views.py:2222 #, fuzzy #| msgid "Create BOM item" msgid "Create BOM Item" msgstr "BOM-Position anlegen" -#: part/views.py:2300 +#: part/views.py:2290 msgid "Edit BOM item" msgstr "BOM-Position beaarbeiten" -#: part/views.py:2350 +#: part/views.py:2340 msgid "Confim BOM item deletion" msgstr "Löschung von BOM-Position bestätigen" @@ -3960,342 +3955,348 @@ msgstr "Einstellungs-Beschreibung" msgid "Enter unique serial numbers (or leave blank)" msgstr "Eindeutige Seriennummern eingeben (oder leer lassen)" -#: stock/forms.py:191 +#: stock/forms.py:192 msgid "Label" msgstr "" -#: stock/forms.py:192 stock/forms.py:248 +#: stock/forms.py:193 stock/forms.py:249 #, fuzzy #| msgid "Select stock item to allocate" msgid "Select test report template" msgstr "Lagerobjekt für Zuordnung auswählen" -#: stock/forms.py:256 +#: stock/forms.py:257 msgid "Include stock items in sub locations" msgstr "Lagerobjekte in untergeordneten Lagerorten einschließen" -#: stock/forms.py:291 +#: stock/forms.py:292 #, fuzzy #| msgid "No stock items matching query" msgid "Stock item to install" msgstr "Keine zur Anfrage passenden Lagerobjekte" -#: stock/forms.py:298 +#: stock/forms.py:299 #, fuzzy #| msgid "Stock Quantity" msgid "Stock quantity to assign" msgstr "Bestand" -#: stock/forms.py:326 +#: stock/forms.py:327 #, fuzzy #| msgid "Quantity must not exceed available stock quantity ({n})" msgid "Must not exceed available quantity" msgstr "Anzahl darf nicht die verfügbare Anzahl überschreiten ({n})" -#: stock/forms.py:336 +#: stock/forms.py:337 #, fuzzy #| msgid "Does this part have tracking for unique items?" msgid "Destination location for uninstalled items" msgstr "Hat dieses Teil Tracking für einzelne Objekte?" -#: stock/forms.py:338 +#: stock/forms.py:339 #, fuzzy #| msgid "Description of the company" msgid "Add transaction note (optional)" msgstr "Firmenbeschreibung" -#: stock/forms.py:340 +#: stock/forms.py:341 #, fuzzy #| msgid "Confirm stock allocation" msgid "Confirm uninstall" msgstr "Lagerbestandszuordnung bestätigen" -#: stock/forms.py:340 +#: stock/forms.py:341 #, fuzzy #| msgid "Confirm movement of stock items" msgid "Confirm removal of installed stock items" msgstr "Bewegung der Lagerobjekte bestätigen" -#: stock/forms.py:364 +#: stock/forms.py:365 msgid "Destination stock location" msgstr "Ziel-Lagerbestand" -#: stock/forms.py:366 +#: stock/forms.py:367 msgid "Add note (required)" msgstr "" -#: stock/forms.py:370 stock/views.py:916 stock/views.py:1114 +#: stock/forms.py:371 stock/views.py:916 stock/views.py:1114 msgid "Confirm stock adjustment" msgstr "Bestands-Anpassung bestätigen" -#: stock/forms.py:370 +#: stock/forms.py:371 msgid "Confirm movement of stock items" msgstr "Bewegung der Lagerobjekte bestätigen" -#: stock/forms.py:372 +#: stock/forms.py:373 #, fuzzy #| msgid "Default Location" msgid "Set Default Location" msgstr "Standard-Lagerort" -#: stock/forms.py:372 +#: stock/forms.py:373 msgid "Set the destination as the default location for selected parts" msgstr "Setze das Ziel als Standard-Ziel für ausgewählte Teile" -#: stock/models.py:178 +#: stock/models.py:179 #, fuzzy #| msgid "Created new stock item" msgid "Created stock item" msgstr "Neues Lagerobjekt erstellt" -#: stock/models.py:214 +#: stock/models.py:215 #, fuzzy #| msgid "A stock item with this serial number already exists" msgid "StockItem with this serial number already exists" msgstr "Ein Teil mit dieser Seriennummer existiert bereits" -#: stock/models.py:250 +#: stock/models.py:251 #, python-brace-format msgid "Part type ('{pf}') must be {pe}" msgstr "Teile-Typ ('{pf}') muss {pe} sein" -#: stock/models.py:260 stock/models.py:269 +#: stock/models.py:261 stock/models.py:270 msgid "Quantity must be 1 for item with a serial number" msgstr "Anzahl muss für Objekte mit Seriennummer \"1\" sein" -#: stock/models.py:261 +#: stock/models.py:262 msgid "Serial number cannot be set if quantity greater than 1" msgstr "" "Seriennummer kann nicht gesetzt werden wenn die Anzahl größer als \"1\" ist" -#: stock/models.py:283 +#: stock/models.py:284 msgid "Item cannot belong to itself" msgstr "Teil kann nicht zu sich selbst gehören" -#: stock/models.py:289 +#: stock/models.py:290 msgid "Item must have a build reference if is_building=True" msgstr "" -#: stock/models.py:296 +#: stock/models.py:297 msgid "Build reference does not point to the same part object" msgstr "" -#: stock/models.py:329 +#: stock/models.py:330 msgid "Parent Stock Item" msgstr "Eltern-Lagerobjekt" -#: stock/models.py:338 +#: stock/models.py:339 msgid "Base part" msgstr "Basis-Teil" -#: stock/models.py:347 +#: stock/models.py:348 msgid "Select a matching supplier part for this stock item" msgstr "Passenden Zulieferer für dieses Lagerobjekt auswählen" -#: stock/models.py:352 stock/templates/stock/stock_app_base.html:7 +#: stock/models.py:353 stock/templates/stock/stock_app_base.html:7 msgid "Stock Location" msgstr "Lagerort" -#: stock/models.py:355 +#: stock/models.py:356 msgid "Where is this stock item located?" msgstr "Wo wird dieses Teil normalerweise gelagert?" -#: stock/models.py:360 stock/templates/stock/item_base.html:212 +#: stock/models.py:361 stock/templates/stock/item_base.html:212 msgid "Installed In" msgstr "Installiert in" -#: stock/models.py:363 +#: stock/models.py:364 msgid "Is this item installed in another item?" msgstr "Ist dieses Teil in einem anderen verbaut?" -#: stock/models.py:379 +#: stock/models.py:380 msgid "Serial number for this item" msgstr "Seriennummer für dieses Teil" -#: stock/models.py:391 +#: stock/models.py:392 msgid "Batch code for this stock item" msgstr "Losnummer für dieses Lagerobjekt" -#: stock/models.py:395 +#: stock/models.py:396 msgid "Stock Quantity" msgstr "Bestand" -#: stock/models.py:404 +#: stock/models.py:405 msgid "Source Build" msgstr "Quellbau" -#: stock/models.py:406 +#: stock/models.py:407 msgid "Build for this stock item" msgstr "Bau für dieses Lagerobjekt" -#: stock/models.py:417 +#: stock/models.py:418 msgid "Source Purchase Order" msgstr "Quellbestellung" -#: stock/models.py:420 +#: stock/models.py:421 msgid "Purchase order for this stock item" msgstr "Bestellung für dieses Teil" -#: stock/models.py:426 +#: stock/models.py:427 msgid "Destination Sales Order" msgstr "Zielauftrag" -#: stock/models.py:433 -msgid "Destination Build Order" -msgstr "Zielbauauftrag" - -#: stock/models.py:446 +#: stock/models.py:439 msgid "Delete this Stock Item when stock is depleted" msgstr "Objekt löschen wenn Lagerbestand aufgebraucht" -#: stock/models.py:456 stock/templates/stock/item_notes.html:14 +#: stock/models.py:449 stock/templates/stock/item_notes.html:14 #: stock/templates/stock/item_notes.html:30 msgid "Stock Item Notes" msgstr "Lagerobjekt-Notizen" -#: stock/models.py:507 +#: stock/models.py:457 stock/templates/stock/item_base.html:266 +#, fuzzy +#| msgid "Purchase Order" +msgid "Purchase Price" +msgstr "Kaufvertrag" + +#: stock/models.py:458 +msgid "Single unit purchase price at time of purchase" +msgstr "" + +#: stock/models.py:509 #, fuzzy #| msgid "Item assigned to customer?" msgid "Assigned to Customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: stock/models.py:509 +#: stock/models.py:511 #, fuzzy #| msgid "Item assigned to customer?" msgid "Manually assigned to customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: stock/models.py:522 +#: stock/models.py:524 #, fuzzy #| msgid "Item assigned to customer?" msgid "Returned from customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: stock/models.py:524 +#: stock/models.py:526 #, fuzzy #| msgid "Create new stock location" msgid "Returned to location" msgstr "Neuen Lagerort anlegen" -#: stock/models.py:652 +#: stock/models.py:651 #, fuzzy #| msgid "Installed in Stock Item" msgid "Installed into stock item" msgstr "In Lagerobjekt installiert" -#: stock/models.py:660 +#: stock/models.py:659 #, fuzzy #| msgid "Installed in Stock Item" msgid "Installed stock item" msgstr "In Lagerobjekt installiert" -#: stock/models.py:684 +#: stock/models.py:683 #, fuzzy #| msgid "Installed in Stock Item" msgid "Uninstalled stock item" msgstr "In Lagerobjekt installiert" -#: stock/models.py:703 +#: stock/models.py:702 #, fuzzy #| msgid "Include sublocations" msgid "Uninstalled into location" msgstr "Unterlagerorte einschließen" -#: stock/models.py:807 +#: stock/models.py:802 #, fuzzy #| msgid "Part is not a virtual part" msgid "Part is not set as trackable" msgstr "Teil ist nicht virtuell" -#: stock/models.py:813 +#: stock/models.py:808 msgid "Quantity must be integer" msgstr "Anzahl muss eine Ganzzahl sein" -#: stock/models.py:819 +#: stock/models.py:814 #, 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:822 +#: stock/models.py:817 msgid "Serial numbers must be a list of integers" msgstr "Seriennummern muss eine Liste von Ganzzahlen sein" -#: stock/models.py:825 +#: stock/models.py:820 msgid "Quantity does not match serial numbers" msgstr "Anzahl stimmt nicht mit den Seriennummern überein" -#: stock/models.py:857 +#: stock/models.py:852 msgid "Add serial number" msgstr "Seriennummer hinzufügen" -#: stock/models.py:860 +#: stock/models.py:855 #, python-brace-format msgid "Serialized {n} items" msgstr "{n} Teile serialisiert" -#: stock/models.py:971 +#: stock/models.py:966 msgid "StockItem cannot be moved as it is not in stock" msgstr "Lagerobjekt kann nicht bewegt werden, da kein Bestand vorhanden ist" -#: stock/models.py:1316 +#: stock/models.py:1320 msgid "Tracking entry title" msgstr "Name des Eintrags-Trackings" -#: stock/models.py:1318 +#: stock/models.py:1322 msgid "Entry notes" msgstr "Eintrags-Notizen" -#: stock/models.py:1320 +#: stock/models.py:1324 msgid "Link to external page for further information" msgstr "Link auf externe Seite für weitere Informationen" -#: stock/models.py:1380 +#: stock/models.py:1384 #, fuzzy #| msgid "Serial number for this item" msgid "Value must be provided for this test" msgstr "Seriennummer für dieses Teil" -#: stock/models.py:1386 +#: stock/models.py:1390 msgid "Attachment must be uploaded for this test" msgstr "" -#: stock/models.py:1403 +#: stock/models.py:1407 msgid "Test" msgstr "" -#: stock/models.py:1404 +#: stock/models.py:1408 #, fuzzy #| msgid "Part name" msgid "Test name" msgstr "Name des Teils" -#: stock/models.py:1409 +#: stock/models.py:1413 #, fuzzy #| msgid "Search Results" msgid "Result" msgstr "Suchergebnisse" -#: stock/models.py:1410 templates/js/table_filters.js:162 +#: stock/models.py:1414 templates/js/table_filters.js:162 msgid "Test result" msgstr "" -#: stock/models.py:1416 +#: stock/models.py:1420 msgid "Test output value" msgstr "" -#: stock/models.py:1422 +#: stock/models.py:1426 #, fuzzy #| msgid "Attachments" msgid "Attachment" msgstr "Anhänge" -#: stock/models.py:1423 +#: stock/models.py:1427 #, fuzzy #| msgid "Delete attachment" msgid "Test result attachment" msgstr "Anhang löschen" -#: stock/models.py:1429 +#: stock/models.py:1433 #, fuzzy #| msgid "Edit notes" msgid "Test notes" @@ -4461,36 +4462,36 @@ msgstr "" msgid "Stock Item Details" msgstr "Lagerbestands-Details" -#: stock/templates/stock/item_base.html:237 templates/js/build.js:426 +#: stock/templates/stock/item_base.html:231 templates/js/build.js:426 #, fuzzy #| msgid "No stock location set" msgid "No location set" msgstr "Kein Lagerort gesetzt" -#: stock/templates/stock/item_base.html:244 +#: stock/templates/stock/item_base.html:238 #, fuzzy #| msgid "Unique Identifier" msgid "Barcode Identifier" msgstr "Eindeutiger Bezeichner" -#: stock/templates/stock/item_base.html:258 templates/js/build.js:626 +#: stock/templates/stock/item_base.html:252 templates/js/build.js:626 #: templates/navbar.html:25 msgid "Build" msgstr "Bau" -#: stock/templates/stock/item_base.html:272 +#: stock/templates/stock/item_base.html:273 msgid "Parent Item" msgstr "Elternposition" -#: stock/templates/stock/item_base.html:297 +#: stock/templates/stock/item_base.html:298 msgid "Last Updated" msgstr "Zuletzt aktualisiert" -#: stock/templates/stock/item_base.html:302 +#: stock/templates/stock/item_base.html:303 msgid "Last Stocktake" msgstr "Letzte Inventur" -#: stock/templates/stock/item_base.html:306 +#: stock/templates/stock/item_base.html:307 msgid "No stocktake performed" msgstr "Keine Inventur ausgeführt" @@ -5019,24 +5020,6 @@ msgstr "Suche" msgid "Build Order Settings" msgstr "Bauaufträge" -#: templates/InvenTree/settings/currency.html:5 -#, fuzzy -#| msgid "Settings" -msgid "General Settings" -msgstr "Einstellungen" - -#: templates/InvenTree/settings/currency.html:14 -#, fuzzy -#| msgid "Currency Value" -msgid "Currencies" -msgstr "Währungs-Wert" - -#: templates/InvenTree/settings/currency.html:18 -#, fuzzy -#| msgid "Delete Currency" -msgid "New Currency" -msgstr "Währung entfernen" - #: templates/InvenTree/settings/global.html:10 #, fuzzy #| msgid "InvenTree Version" @@ -5055,21 +5038,21 @@ msgstr "Einstellungen" msgid "Part Options" msgstr "Quell-Standort" -#: templates/InvenTree/settings/part.html:31 +#: templates/InvenTree/settings/part.html:33 #, fuzzy #| msgid "Edit Part Parameter Template" msgid "Part Parameter Templates" msgstr "Teilparametervorlage bearbeiten" -#: templates/InvenTree/settings/part.html:52 +#: templates/InvenTree/settings/part.html:54 msgid "No part parameter templates found" msgstr "Keine Teilparametervorlagen gefunden" -#: templates/InvenTree/settings/part.html:72 +#: templates/InvenTree/settings/part.html:74 msgid "Edit Template" msgstr "Vorlage bearbeiten" -#: templates/InvenTree/settings/part.html:73 +#: templates/InvenTree/settings/part.html:75 msgid "Delete Template" msgstr "Vorlage löschen" @@ -5131,12 +5114,6 @@ msgstr "InvenTree-Version" msgid "Global" msgstr "" -#: templates/InvenTree/settings/tabs.html:19 -#, fuzzy -#| msgid "Edit Currency" -msgid "Currency" -msgstr "Währung bearbeiten" - #: templates/InvenTree/settings/theme.html:10 #, fuzzy #| msgid "Settings" @@ -6032,46 +6009,93 @@ msgstr "Revision" msgid "Important dates" msgstr "Stückliste importieren" -#: users/models.py:130 +#: users/models.py:131 msgid "Permission set" msgstr "" -#: users/models.py:138 +#: users/models.py:139 msgid "Group" msgstr "" -#: users/models.py:141 +#: users/models.py:142 msgid "View" msgstr "" -#: users/models.py:141 +#: users/models.py:142 msgid "Permission to view items" msgstr "" -#: users/models.py:143 +#: users/models.py:144 #, fuzzy #| msgid "Address" msgid "Add" msgstr "Adresse" -#: users/models.py:143 +#: users/models.py:144 msgid "Permission to add items" msgstr "" -#: users/models.py:145 +#: users/models.py:146 msgid "Change" msgstr "" -#: users/models.py:145 +#: users/models.py:146 msgid "Permissions to edit items" msgstr "" -#: users/models.py:147 +#: users/models.py:148 #, fuzzy #| msgid "Remove selected BOM items" msgid "Permission to delete items" msgstr "Ausgewählte Stücklistenpositionen entfernen" +#~ msgid "Currency Symbol e.g. $" +#~ msgstr "Währungs-Symbol (z.B. €)" + +#~ msgid "Currency Suffix e.g. AUD" +#~ msgstr "Währungs-Suffix (z.B. EUR)" + +#~ msgid "Currency Description" +#~ msgstr "Währungs-Beschreibung" + +#~ msgid "Currency Value" +#~ msgstr "Währungs-Wert" + +#~ msgid "Use this currency as the base currency" +#~ msgstr "Benutze diese Währung als Basis-Währung" + +#~ msgid "Create new Currency" +#~ msgstr "Neues Währung hinzufügen" + +#~ msgid "Edit Currency" +#~ msgstr "Währung bearbeiten" + +#~ msgid "Select currency for price calculation" +#~ msgstr "Währung zur Preisberechnung wählen" + +#~ msgid "Destination Build Order" +#~ msgstr "Zielbauauftrag" + +#, fuzzy +#~| msgid "Settings" +#~ msgid "General Settings" +#~ msgstr "Einstellungen" + +#, fuzzy +#~| msgid "Currency Value" +#~ msgid "Currencies" +#~ msgstr "Währungs-Wert" + +#, fuzzy +#~| msgid "Delete Currency" +#~ msgid "New Currency" +#~ msgstr "Währung entfernen" + +#, fuzzy +#~| msgid "Edit Currency" +#~ msgid "Currency" +#~ msgstr "Währung bearbeiten" + #, fuzzy #~| msgid "Serial Number" #~ msgid "Serial Numbers" diff --git a/InvenTree/locale/en/LC_MESSAGES/django.po b/InvenTree/locale/en/LC_MESSAGES/django.po index c4c14849c2..976e28c2c6 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-11-09 12:47+0000\n" +"POT-Creation-Date: 2020-11-10 13:31+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -26,23 +26,23 @@ msgstr "" msgid "No matching action found" msgstr "" -#: InvenTree/forms.py:130 build/forms.py:82 build/forms.py:170 +#: InvenTree/forms.py:107 build/forms.py:82 build/forms.py:170 msgid "Confirm" msgstr "" -#: InvenTree/forms.py:146 +#: InvenTree/forms.py:123 msgid "Confirm item deletion" msgstr "" -#: InvenTree/forms.py:178 +#: InvenTree/forms.py:155 msgid "Enter new password" msgstr "" -#: InvenTree/forms.py:185 +#: InvenTree/forms.py:162 msgid "Confirm new password" msgstr "" -#: InvenTree/forms.py:220 +#: InvenTree/forms.py:197 msgid "Apply Theme" msgstr "" @@ -99,19 +99,19 @@ msgstr "" msgid "Description (optional)" msgstr "" -#: InvenTree/settings.py:348 +#: InvenTree/settings.py:350 msgid "English" msgstr "" -#: InvenTree/settings.py:349 +#: InvenTree/settings.py:351 msgid "German" msgstr "" -#: InvenTree/settings.py:350 +#: InvenTree/settings.py:352 msgid "French" msgstr "" -#: InvenTree/settings.py:351 +#: InvenTree/settings.py:353 msgid "Polish" msgstr "" @@ -280,7 +280,7 @@ msgstr "" #: order/templates/order/sales_order_detail.html:156 #: part/templates/part/allocation.html:16 #: part/templates/part/allocation.html:49 -#: part/templates/part/sale_prices.html:80 stock/forms.py:297 +#: part/templates/part/sale_prices.html:82 stock/forms.py:298 #: stock/templates/stock/item_base.html:40 #: stock/templates/stock/item_base.html:46 #: stock/templates/stock/item_base.html:197 @@ -345,14 +345,13 @@ msgstr "" #: build/models.py:56 build/templates/build/build_base.html:8 #: build/templates/build/build_base.html:35 #: part/templates/part/allocation.html:20 -#: stock/templates/stock/item_base.html:227 msgid "Build Order" msgstr "" #: build/models.py:57 build/templates/build/index.html:6 #: build/templates/build/index.html:14 order/templates/order/so_builds.html:11 #: order/templates/order/so_tabs.html:9 part/templates/part/tabs.html:31 -#: templates/InvenTree/settings/tabs.html:28 users/models.py:30 +#: templates/InvenTree/settings/tabs.html:25 users/models.py:30 msgid "Build Orders" msgstr "" @@ -460,7 +459,7 @@ msgstr "" msgid "Build status code" msgstr "" -#: build/models.py:157 stock/models.py:389 +#: build/models.py:157 stock/models.py:390 msgid "Batch Code" msgstr "" @@ -472,11 +471,11 @@ msgstr "" #: company/templates/company/supplier_part_base.html:68 #: company/templates/company/supplier_part_detail.html:24 #: part/templates/part/detail.html:80 part/templates/part/part_base.html:102 -#: stock/models.py:383 stock/templates/stock/item_base.html:279 +#: stock/models.py:384 stock/templates/stock/item_base.html:280 msgid "External Link" msgstr "" -#: build/models.py:177 part/models.py:597 stock/models.py:385 +#: build/models.py:177 part/models.py:609 stock/models.py:386 msgid "Link to external URL" msgstr "" @@ -484,8 +483,8 @@ msgstr "" #: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:18 #: order/templates/order/purchase_order_detail.html:203 #: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:73 -#: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:455 -#: stock/models.py:1428 stock/templates/stock/tabs.html:26 +#: stock/forms.py:307 stock/forms.py:339 stock/forms.py:367 stock/models.py:448 +#: stock/models.py:1432 stock/templates/stock/tabs.html:26 #: templates/js/barcode.js:391 templates/js/bom.js:250 #: templates/js/stock.js:116 templates/js/stock.js:578 msgid "Notes" @@ -549,11 +548,11 @@ msgstr "" msgid "Source stock item" msgstr "" -#: build/models.py:976 +#: build/models.py:975 msgid "Stock quantity to allocate to build" msgstr "" -#: build/models.py:984 +#: build/models.py:983 msgid "Destination stock item" msgstr "" @@ -618,8 +617,8 @@ msgid "" "The following stock items will be allocated to the specified build output" msgstr "" -#: build/templates/build/auto_allocate.html:18 stock/forms.py:336 -#: stock/templates/stock/item_base.html:233 +#: build/templates/build/auto_allocate.html:18 stock/forms.py:337 +#: stock/templates/stock/item_base.html:227 #: stock/templates/stock/stock_adjust.html:17 #: templates/InvenTree/search.html:183 templates/js/barcode.js:337 #: templates/js/build.js:418 templates/js/stock.js:570 @@ -675,7 +674,7 @@ msgstr "" #: build/templates/build/build_base.html:83 #: build/templates/build/detail.html:57 #: order/templates/order/receive_parts.html:24 -#: stock/templates/stock/item_base.html:311 templates/InvenTree/search.html:175 +#: stock/templates/stock/item_base.html:312 templates/InvenTree/search.html:175 #: templates/js/barcode.js:42 templates/js/build.js:675 #: templates/js/order.js:172 templates/js/order.js:254 #: templates/js/stock.js:557 templates/js/stock.js:961 @@ -786,7 +785,7 @@ msgstr "" msgid "Stock can be taken from any available location." msgstr "" -#: build/templates/build/detail.html:44 stock/forms.py:364 +#: build/templates/build/detail.html:44 stock/forms.py:365 msgid "Destination" msgstr "" @@ -795,7 +794,7 @@ msgid "Destination location not specified" msgstr "" #: build/templates/build/detail.html:68 -#: stock/templates/stock/item_base.html:251 templates/js/stock.js:565 +#: stock/templates/stock/item_base.html:245 templates/js/stock.js:565 #: templates/js/stock.js:968 templates/js/table_filters.js:80 #: templates/js/table_filters.js:151 msgid "Batch" @@ -887,7 +886,7 @@ msgstr "" msgid "Create Build Output" msgstr "" -#: build/views.py:207 stock/models.py:832 stock/views.py:1647 +#: build/views.py:207 stock/models.py:827 stock/views.py:1647 msgid "Serial numbers already exist" msgstr "" @@ -992,7 +991,7 @@ msgstr "" msgid "Add Build Order Attachment" msgstr "" -#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:164 +#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:166 #: stock/views.py:176 msgid "Added attachment" msgstr "" @@ -1013,158 +1012,163 @@ msgstr "" msgid "Deleted attachment" msgstr "" -#: common/models.py:51 +#: common/models.py:55 msgid "InvenTree Instance Name" msgstr "" -#: common/models.py:53 +#: common/models.py:57 msgid "String descriptor for the server instance" msgstr "" -#: common/models.py:57 company/models.py:89 company/models.py:90 +#: common/models.py:61 company/models.py:89 company/models.py:90 msgid "Company name" msgstr "" -#: common/models.py:58 +#: common/models.py:62 msgid "Internal company name" msgstr "" -#: common/models.py:63 -msgid "IPN Regex" -msgstr "" - -#: common/models.py:64 -msgid "Regular expression pattern for matching Part IPN" +#: common/models.py:67 +msgid "Default Currency" msgstr "" #: common/models.py:68 -msgid "Copy Part BOM Data" +msgid "Default currency" msgstr "" -#: common/models.py:69 -msgid "Copy BOM data by default when duplicating a part" +#: common/models.py:74 +msgid "IPN Regex" msgstr "" #: common/models.py:75 +msgid "Regular expression pattern for matching Part IPN" +msgstr "" + +#: common/models.py:79 +msgid "Allow Duplicate IPN" +msgstr "" + +#: common/models.py:80 +msgid "Allow multiple parts to share the same IPN" +msgstr "" + +#: common/models.py:86 +msgid "Copy Part BOM Data" +msgstr "" + +#: common/models.py:87 +msgid "Copy BOM data by default when duplicating a part" +msgstr "" + +#: common/models.py:93 msgid "Copy Part Parameter Data" msgstr "" -#: common/models.py:76 +#: common/models.py:94 msgid "Copy parameter data by default when duplicating a part" msgstr "" -#: common/models.py:82 +#: common/models.py:100 msgid "Copy Part Test Data" msgstr "" -#: common/models.py:83 +#: common/models.py:101 msgid "Copy test data by default when duplicating a part" msgstr "" -#: common/models.py:89 part/models.py:668 part/templates/part/detail.html:168 +#: common/models.py:107 part/models.py:680 part/templates/part/detail.html:168 #: templates/js/table_filters.js:264 msgid "Component" msgstr "" -#: common/models.py:90 +#: common/models.py:108 msgid "Parts can be used as sub-components by default" msgstr "" -#: common/models.py:96 part/models.py:679 part/templates/part/detail.html:188 +#: common/models.py:114 part/models.py:691 part/templates/part/detail.html:188 msgid "Purchaseable" msgstr "" -#: common/models.py:97 +#: common/models.py:115 msgid "Parts are purchaseable by default" msgstr "" -#: common/models.py:103 part/models.py:684 part/templates/part/detail.html:198 +#: common/models.py:121 part/models.py:696 part/templates/part/detail.html:198 #: templates/js/table_filters.js:272 msgid "Salable" msgstr "" -#: common/models.py:104 +#: common/models.py:122 msgid "Parts are salable by default" msgstr "" -#: common/models.py:110 part/models.py:674 part/templates/part/detail.html:178 +#: common/models.py:128 part/models.py:686 part/templates/part/detail.html:178 #: templates/js/table_filters.js:31 templates/js/table_filters.js:276 msgid "Trackable" msgstr "" -#: common/models.py:111 +#: common/models.py:129 msgid "Parts are trackable by default" msgstr "" -#: common/models.py:117 +#: common/models.py:135 msgid "Build Order Reference Prefix" msgstr "" -#: common/models.py:118 +#: common/models.py:136 msgid "Prefix value for build order reference" msgstr "" -#: common/models.py:123 +#: common/models.py:141 msgid "Build Order Reference Regex" msgstr "" -#: common/models.py:124 +#: common/models.py:142 msgid "Regular expression pattern for matching build order reference" msgstr "" -#: common/models.py:128 +#: common/models.py:146 msgid "Sales Order Reference Prefix" msgstr "" -#: common/models.py:129 +#: common/models.py:147 msgid "Prefix value for sales order reference" msgstr "" -#: common/models.py:133 +#: common/models.py:151 msgid "Purchase Order Reference Prefix" msgstr "" -#: common/models.py:134 +#: common/models.py:152 msgid "Prefix value for purchase order reference" msgstr "" -#: common/models.py:312 +#: common/models.py:357 msgid "Settings key (must be unique - case insensitive" msgstr "" -#: common/models.py:314 +#: common/models.py:359 msgid "Settings value" msgstr "" -#: common/models.py:366 +#: common/models.py:415 msgid "Value must be a boolean value" msgstr "" -#: common/models.py:380 +#: common/models.py:429 msgid "Key string must be unique" msgstr "" -#: common/models.py:419 -msgid "Currency Symbol e.g. $" +#: common/models.py:474 company/templates/company/supplier_part_pricing.html:80 +#: part/templates/part/sale_prices.html:87 templates/js/bom.js:234 +msgid "Price" msgstr "" -#: common/models.py:421 -msgid "Currency Suffix e.g. AUD" +#: common/models.py:475 +msgid "Unit price at specified quantity" msgstr "" -#: common/models.py:423 -msgid "Currency Description" -msgstr "" - -#: common/models.py:425 -msgid "Currency Value" -msgstr "" - -#: common/models.py:427 -msgid "Use this currency as the base currency" -msgstr "" - -#: common/models.py:510 +#: common/models.py:498 msgid "Default" msgstr "" @@ -1172,19 +1176,7 @@ msgstr "" msgid "Current value" msgstr "" -#: common/views.py:23 -msgid "Create new Currency" -msgstr "" - -#: common/views.py:31 -msgid "Edit Currency" -msgstr "" - -#: common/views.py:38 -msgid "Delete Currency" -msgstr "" - -#: common/views.py:49 +#: common/views.py:25 msgid "Change Setting" msgstr "" @@ -1253,7 +1245,7 @@ msgstr "" msgid "Does this company manufacture parts?" msgstr "" -#: company/models.py:283 stock/models.py:337 +#: company/models.py:283 stock/models.py:338 #: stock/templates/stock/item_base.html:177 msgid "Base Part" msgstr "" @@ -1325,14 +1317,14 @@ msgstr "" #: company/templates/company/supplier_part_detail.html:21 #: order/templates/order/order_base.html:79 #: order/templates/order/order_wizard/select_pos.html:30 part/bom.py:170 -#: stock/templates/stock/item_base.html:286 templates/js/company.js:48 +#: stock/templates/stock/item_base.html:287 templates/js/company.js:48 #: templates/js/company.js:164 templates/js/order.js:154 msgid "Supplier" msgstr "" #: company/templates/company/detail.html:26 -#: order/templates/order/sales_order_base.html:81 stock/models.py:372 -#: stock/models.py:373 stock/templates/stock/item_base.html:204 +#: order/templates/order/sales_order_base.html:81 stock/models.py:373 +#: stock/models.py:374 stock/templates/stock/item_base.html:204 #: templates/js/company.js:40 templates/js/order.js:236 msgid "Customer" msgstr "" @@ -1380,21 +1372,21 @@ msgstr "" msgid "Create new Part" msgstr "" -#: company/templates/company/detail_part.html:69 company/views.py:53 +#: company/templates/company/detail_part.html:69 company/views.py:55 #: part/templates/part/supplier.html:47 msgid "New Supplier" msgstr "" -#: company/templates/company/detail_part.html:70 company/views.py:192 +#: company/templates/company/detail_part.html:70 company/views.py:194 msgid "Create new Supplier" msgstr "" -#: company/templates/company/detail_part.html:75 company/views.py:60 +#: company/templates/company/detail_part.html:75 company/views.py:62 #: part/templates/part/supplier.html:53 msgid "New Manufacturer" msgstr "" -#: company/templates/company/detail_part.html:76 company/views.py:195 +#: company/templates/company/detail_part.html:76 company/views.py:197 msgid "Create new Manufacturer" msgstr "" @@ -1428,7 +1420,7 @@ msgstr "" #: order/templates/order/purchase_orders.html:7 #: order/templates/order/purchase_orders.html:12 #: part/templates/part/orders.html:9 part/templates/part/tabs.html:48 -#: templates/InvenTree/settings/tabs.html:31 templates/navbar.html:33 +#: templates/InvenTree/settings/tabs.html:28 templates/navbar.html:33 #: users/models.py:31 msgid "Purchase Orders" msgstr "" @@ -1448,7 +1440,7 @@ msgstr "" #: order/templates/order/sales_orders.html:7 #: order/templates/order/sales_orders.html:12 #: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:56 -#: templates/InvenTree/settings/tabs.html:34 templates/navbar.html:42 +#: templates/InvenTree/settings/tabs.html:31 templates/navbar.html:42 #: users/models.py:32 msgid "Sales Orders" msgstr "" @@ -1464,8 +1456,8 @@ msgid "New Sales Order" msgstr "" #: company/templates/company/supplier_part_base.html:6 -#: company/templates/company/supplier_part_base.html:19 stock/models.py:346 -#: stock/templates/stock/item_base.html:291 templates/js/company.js:180 +#: company/templates/company/supplier_part_base.html:19 stock/models.py:347 +#: stock/templates/stock/item_base.html:292 templates/js/company.js:180 msgid "Supplier Part" msgstr "" @@ -1521,28 +1513,23 @@ msgstr "" msgid "Pricing Information" msgstr "" -#: company/templates/company/supplier_part_pricing.html:17 company/views.py:410 -#: part/templates/part/sale_prices.html:13 part/views.py:2360 +#: company/templates/company/supplier_part_pricing.html:17 company/views.py:412 +#: part/templates/part/sale_prices.html:14 part/views.py:2350 msgid "Add Price Break" msgstr "" #: company/templates/company/supplier_part_pricing.html:36 -#: part/templates/part/sale_prices.html:41 +#: part/templates/part/sale_prices.html:43 msgid "No price break information found" msgstr "" -#: company/templates/company/supplier_part_pricing.html:80 -#: part/templates/part/sale_prices.html:85 templates/js/bom.js:234 -msgid "Price" -msgstr "" - -#: company/templates/company/supplier_part_pricing.html:94 -#: part/templates/part/sale_prices.html:99 +#: company/templates/company/supplier_part_pricing.html:87 +#: part/templates/part/sale_prices.html:94 msgid "Edit price break" msgstr "" -#: company/templates/company/supplier_part_pricing.html:95 -#: part/templates/part/sale_prices.html:100 +#: company/templates/company/supplier_part_pricing.html:88 +#: part/templates/part/sale_prices.html:95 msgid "Delete price break" msgstr "" @@ -1557,7 +1544,7 @@ msgstr "" #: company/templates/company/supplier_part_tabs.html:8 #: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18 #: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155 -#: templates/InvenTree/settings/tabs.html:25 templates/js/part.js:192 +#: templates/InvenTree/settings/tabs.html:22 templates/js/part.js:192 #: templates/js/part.js:418 templates/js/stock.js:502 templates/navbar.html:22 #: users/models.py:29 msgid "Stock" @@ -1571,93 +1558,93 @@ msgstr "" #: order/templates/order/receive_parts.html:14 part/models.py:295 #: part/templates/part/cat_link.html:7 part/templates/part/category.html:94 #: part/templates/part/category_tabs.html:6 -#: templates/InvenTree/settings/tabs.html:22 templates/navbar.html:19 +#: templates/InvenTree/settings/tabs.html:19 templates/navbar.html:19 #: templates/stats.html:8 templates/stats.html:17 users/models.py:28 msgid "Parts" msgstr "" -#: company/views.py:52 part/templates/part/tabs.html:42 +#: company/views.py:54 part/templates/part/tabs.html:42 #: templates/navbar.html:31 msgid "Suppliers" msgstr "" -#: company/views.py:59 templates/navbar.html:32 +#: company/views.py:61 templates/navbar.html:32 msgid "Manufacturers" msgstr "" -#: company/views.py:66 templates/navbar.html:41 +#: company/views.py:68 templates/navbar.html:41 msgid "Customers" msgstr "" -#: company/views.py:67 +#: company/views.py:69 msgid "New Customer" msgstr "" -#: company/views.py:75 +#: company/views.py:77 msgid "Companies" msgstr "" -#: company/views.py:76 +#: company/views.py:78 msgid "New Company" msgstr "" -#: company/views.py:154 +#: company/views.py:156 msgid "Update Company Image" msgstr "" -#: company/views.py:160 +#: company/views.py:162 msgid "Updated company image" msgstr "" -#: company/views.py:170 +#: company/views.py:172 msgid "Edit Company" msgstr "" -#: company/views.py:175 +#: company/views.py:177 msgid "Edited company information" msgstr "" -#: company/views.py:198 +#: company/views.py:200 msgid "Create new Customer" msgstr "" -#: company/views.py:200 +#: company/views.py:202 msgid "Create new Company" msgstr "" -#: company/views.py:227 +#: company/views.py:229 msgid "Created new company" msgstr "" -#: company/views.py:237 +#: company/views.py:239 msgid "Delete Company" msgstr "" -#: company/views.py:243 +#: company/views.py:245 msgid "Company was deleted" msgstr "" -#: company/views.py:268 +#: company/views.py:270 msgid "Edit Supplier Part" msgstr "" -#: company/views.py:278 templates/js/stock.js:846 +#: company/views.py:280 templates/js/stock.js:846 msgid "Create new Supplier Part" msgstr "" -#: company/views.py:339 +#: company/views.py:341 msgid "Delete Supplier Part" msgstr "" -#: company/views.py:416 part/views.py:2366 +#: company/views.py:418 part/views.py:2356 msgid "Added new price break" msgstr "" -#: company/views.py:453 part/views.py:2411 +#: company/views.py:454 part/views.py:2400 msgid "Edit Price Break" msgstr "" -#: company/views.py:469 part/views.py:2427 +#: company/views.py:470 part/views.py:2416 msgid "Delete Price Break" msgstr "" @@ -1750,8 +1737,8 @@ msgstr "" msgid "Date order was completed" msgstr "" -#: order/models.py:185 order/models.py:267 part/views.py:1477 -#: stock/models.py:243 stock/models.py:816 +#: order/models.py:185 order/models.py:267 part/views.py:1479 +#: stock/models.py:244 stock/models.py:811 msgid "Quantity must be greater than zero" msgstr "" @@ -1789,7 +1776,7 @@ msgstr "" #: order/models.py:484 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 -#: stock/templates/stock/item_base.html:265 templates/js/order.js:139 +#: stock/templates/stock/item_base.html:259 templates/js/order.js:139 msgid "Purchase Order" msgstr "" @@ -1817,11 +1804,11 @@ msgstr "" msgid "Quantity must be 1 for serialized stock item" msgstr "" -#: order/models.py:626 +#: order/models.py:625 msgid "Select stock item to allocate" msgstr "" -#: order/models.py:629 +#: order/models.py:628 msgid "Enter stock allocation quantity" msgstr "" @@ -2032,7 +2019,7 @@ msgid "Sales Order Items" msgstr "" #: order/templates/order/sales_order_detail.html:72 -#: order/templates/order/sales_order_detail.html:154 stock/models.py:377 +#: order/templates/order/sales_order_detail.html:154 stock/models.py:378 #: stock/templates/stock/item_base.html:191 templates/js/build.js:402 msgid "Serial Number" msgstr "" @@ -2269,114 +2256,110 @@ msgstr "" msgid "Error reading BOM file (incorrect row size)" msgstr "" -#: part/forms.py:62 stock/forms.py:254 +#: part/forms.py:60 stock/forms.py:255 msgid "File Format" msgstr "" -#: part/forms.py:62 stock/forms.py:254 +#: part/forms.py:60 stock/forms.py:255 msgid "Select output file format" msgstr "" -#: part/forms.py:64 +#: part/forms.py:62 msgid "Cascading" msgstr "" -#: part/forms.py:64 +#: part/forms.py:62 msgid "Download cascading / multi-level BOM" msgstr "" -#: part/forms.py:66 +#: part/forms.py:64 msgid "Levels" msgstr "" -#: part/forms.py:66 +#: part/forms.py:64 msgid "Select maximum number of BOM levels to export (0 = all levels)" msgstr "" -#: part/forms.py:68 +#: part/forms.py:66 msgid "Include Parameter Data" msgstr "" -#: part/forms.py:68 +#: part/forms.py:66 msgid "Include part parameters data in exported BOM" msgstr "" -#: part/forms.py:70 +#: part/forms.py:68 msgid "Include Stock Data" msgstr "" -#: part/forms.py:70 +#: part/forms.py:68 msgid "Include part stock data in exported BOM" msgstr "" -#: part/forms.py:72 +#: part/forms.py:70 msgid "Include Supplier Data" msgstr "" -#: part/forms.py:72 +#: part/forms.py:70 msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:93 part/models.py:1632 +#: part/forms.py:91 part/models.py:1644 msgid "Parent Part" msgstr "" -#: part/forms.py:94 part/templates/part/bom_duplicate.html:7 +#: part/forms.py:92 part/templates/part/bom_duplicate.html:7 msgid "Select parent part to copy BOM from" msgstr "" -#: part/forms.py:100 +#: part/forms.py:98 msgid "Clear existing BOM items" msgstr "" -#: part/forms.py:105 +#: part/forms.py:103 msgid "Confirm BOM duplication" msgstr "" -#: part/forms.py:123 +#: part/forms.py:121 msgid "Confirm that the BOM is correct" msgstr "" -#: part/forms.py:135 +#: part/forms.py:133 msgid "Select BOM file to upload" msgstr "" -#: part/forms.py:154 +#: part/forms.py:152 msgid "Related Part" msgstr "" -#: part/forms.py:173 +#: part/forms.py:171 msgid "Select part category" msgstr "" -#: part/forms.py:189 +#: part/forms.py:187 msgid "Duplicate all BOM data for this part" msgstr "" -#: part/forms.py:190 +#: part/forms.py:188 msgid "Copy BOM" msgstr "" -#: part/forms.py:195 +#: part/forms.py:193 msgid "Duplicate all parameter data for this part" msgstr "" -#: part/forms.py:196 +#: part/forms.py:194 msgid "Copy Parameters" msgstr "" -#: part/forms.py:201 +#: part/forms.py:199 msgid "Confirm part creation" msgstr "" -#: part/forms.py:298 +#: part/forms.py:296 msgid "Input quantity for price calculation" msgstr "" -#: part/forms.py:301 -msgid "Select currency for price calculation" -msgstr "" - #: part/models.py:67 msgid "Default location for parts in this category" msgstr "" @@ -2411,225 +2394,229 @@ msgstr "" msgid "Most recent serial number is" msgstr "" -#: part/models.py:540 +#: part/models.py:541 +msgid "Duplicate IPN not allowed in part settings" +msgstr "" + +#: part/models.py:552 msgid "Part must be unique for name, IPN and revision" msgstr "" -#: part/models.py:569 part/templates/part/detail.html:19 +#: part/models.py:581 part/templates/part/detail.html:19 msgid "Part name" msgstr "" -#: part/models.py:573 +#: part/models.py:585 msgid "Is this part a template part?" msgstr "" -#: part/models.py:582 +#: part/models.py:594 msgid "Is this part a variant of another part?" msgstr "" -#: part/models.py:584 +#: part/models.py:596 msgid "Part description" msgstr "" -#: part/models.py:586 +#: part/models.py:598 msgid "Part keywords to improve visibility in search results" msgstr "" -#: part/models.py:591 +#: part/models.py:603 msgid "Part category" msgstr "" -#: part/models.py:593 +#: part/models.py:605 msgid "Internal Part Number" msgstr "" -#: part/models.py:595 +#: part/models.py:607 msgid "Part revision or version number" msgstr "" -#: part/models.py:609 +#: part/models.py:621 msgid "Where is this item normally stored?" msgstr "" -#: part/models.py:653 +#: part/models.py:665 msgid "Default supplier part" msgstr "" -#: part/models.py:656 +#: part/models.py:668 msgid "Minimum allowed stock level" msgstr "" -#: part/models.py:658 +#: part/models.py:670 msgid "Stock keeping units for this part" msgstr "" -#: part/models.py:662 part/templates/part/detail.html:158 +#: part/models.py:674 part/templates/part/detail.html:158 #: templates/js/table_filters.js:260 msgid "Assembly" msgstr "" -#: part/models.py:663 +#: part/models.py:675 msgid "Can this part be built from other parts?" msgstr "" -#: part/models.py:669 +#: part/models.py:681 msgid "Can this part be used to build other parts?" msgstr "" -#: part/models.py:675 +#: part/models.py:687 msgid "Does this part have tracking for unique items?" msgstr "" -#: part/models.py:680 +#: part/models.py:692 msgid "Can this part be purchased from external suppliers?" msgstr "" -#: part/models.py:685 +#: part/models.py:697 msgid "Can this part be sold to customers?" msgstr "" -#: part/models.py:689 part/templates/part/detail.html:215 +#: part/models.py:701 part/templates/part/detail.html:215 #: templates/js/table_filters.js:19 templates/js/table_filters.js:55 #: templates/js/table_filters.js:186 templates/js/table_filters.js:243 msgid "Active" msgstr "" -#: part/models.py:690 +#: part/models.py:702 msgid "Is this part active?" msgstr "" -#: part/models.py:694 part/templates/part/detail.html:138 +#: part/models.py:706 part/templates/part/detail.html:138 #: templates/js/table_filters.js:27 msgid "Virtual" msgstr "" -#: part/models.py:695 +#: part/models.py:707 msgid "Is this a virtual part, such as a software product or license?" msgstr "" -#: part/models.py:697 +#: part/models.py:709 msgid "Part notes - supports Markdown formatting" msgstr "" -#: part/models.py:699 +#: part/models.py:711 msgid "Stored BOM checksum" msgstr "" -#: part/models.py:1505 +#: part/models.py:1517 msgid "Test templates can only be created for trackable parts" msgstr "" -#: part/models.py:1522 +#: part/models.py:1534 msgid "Test with this name already exists for this part" msgstr "" -#: part/models.py:1541 templates/js/part.js:567 templates/js/stock.js:92 +#: part/models.py:1553 templates/js/part.js:567 templates/js/stock.js:92 msgid "Test Name" msgstr "" -#: part/models.py:1542 +#: part/models.py:1554 msgid "Enter a name for the test" msgstr "" -#: part/models.py:1547 +#: part/models.py:1559 msgid "Test Description" msgstr "" -#: part/models.py:1548 +#: part/models.py:1560 msgid "Enter description for this test" msgstr "" -#: part/models.py:1553 templates/js/part.js:576 +#: part/models.py:1565 templates/js/part.js:576 #: templates/js/table_filters.js:172 msgid "Required" msgstr "" -#: part/models.py:1554 +#: part/models.py:1566 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1559 templates/js/part.js:584 +#: part/models.py:1571 templates/js/part.js:584 msgid "Requires Value" msgstr "" -#: part/models.py:1560 +#: part/models.py:1572 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1565 templates/js/part.js:591 +#: part/models.py:1577 templates/js/part.js:591 msgid "Requires Attachment" msgstr "" -#: part/models.py:1566 +#: part/models.py:1578 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1599 +#: part/models.py:1611 msgid "Parameter template name must be unique" msgstr "" -#: part/models.py:1604 +#: part/models.py:1616 msgid "Parameter Name" msgstr "" -#: part/models.py:1606 +#: part/models.py:1618 msgid "Parameter Units" msgstr "" -#: part/models.py:1634 +#: part/models.py:1646 msgid "Parameter Template" msgstr "" -#: part/models.py:1636 +#: part/models.py:1648 msgid "Parameter Value" msgstr "" -#: part/models.py:1673 +#: part/models.py:1685 msgid "Select parent part" msgstr "" -#: part/models.py:1681 +#: part/models.py:1693 msgid "Select part to be used in BOM" msgstr "" -#: part/models.py:1687 +#: part/models.py:1699 msgid "BOM quantity for this BOM item" msgstr "" -#: part/models.py:1689 +#: part/models.py:1701 msgid "This BOM item is optional" msgstr "" -#: part/models.py:1692 +#: part/models.py:1704 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "" -#: part/models.py:1695 +#: part/models.py:1707 msgid "BOM item reference" msgstr "" -#: part/models.py:1698 +#: part/models.py:1710 msgid "BOM item notes" msgstr "" -#: part/models.py:1700 +#: part/models.py:1712 msgid "BOM line checksum" msgstr "" -#: part/models.py:1767 part/views.py:1483 part/views.py:1535 -#: stock/models.py:233 +#: part/models.py:1779 part/views.py:1485 part/views.py:1537 +#: stock/models.py:234 msgid "Quantity must be integer value for trackable parts" msgstr "" -#: part/models.py:1783 +#: part/models.py:1795 msgid "BOM Item" msgstr "" -#: part/models.py:1898 +#: part/models.py:1910 msgid "Select Related Part" msgstr "" -#: part/models.py:1930 +#: part/models.py:1942 msgid "" "Error creating relationship: check that the part is not related to itself " "and that the relationship is unique" @@ -2650,7 +2637,7 @@ msgstr "" #: part/templates/part/allocation.html:45 #: stock/templates/stock/item_base.html:8 #: stock/templates/stock/item_base.html:72 -#: stock/templates/stock/item_base.html:273 +#: stock/templates/stock/item_base.html:274 #: stock/templates/stock/stock_adjust.html:16 templates/js/build.js:724 #: templates/js/stock.js:695 templates/js/stock.js:944 msgid "Stock Item" @@ -2717,7 +2704,7 @@ msgstr "" msgid "Validate" msgstr "" -#: part/templates/part/bom.html:62 part/views.py:1774 +#: part/templates/part/bom.html:62 part/views.py:1776 msgid "Export Bill of Materials" msgstr "" @@ -2813,7 +2800,7 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:24 part/views.py:2177 +#: part/templates/part/category.html:24 part/views.py:2167 msgid "Create new part category" msgstr "" @@ -2967,7 +2954,7 @@ msgstr "" msgid "Part is not a virtual part" msgstr "" -#: part/templates/part/detail.html:148 stock/forms.py:248 +#: part/templates/part/detail.html:148 stock/forms.py:249 #: templates/js/table_filters.js:23 templates/js/table_filters.js:248 msgid "Template" msgstr "" @@ -3036,17 +3023,17 @@ msgstr "" msgid "Add new parameter" msgstr "" -#: part/templates/part/params.html:15 templates/InvenTree/settings/part.html:35 +#: part/templates/part/params.html:15 templates/InvenTree/settings/part.html:37 msgid "New Parameter" msgstr "" -#: part/templates/part/params.html:25 stock/models.py:1415 +#: part/templates/part/params.html:25 stock/models.py:1419 #: templates/js/stock.js:112 msgid "Value" msgstr "" #: part/templates/part/params.html:41 part/templates/part/related.html:41 -#: part/templates/part/supplier.html:19 users/models.py:147 +#: part/templates/part/supplier.html:19 users/models.py:148 msgid "Delete" msgstr "" @@ -3226,7 +3213,7 @@ msgstr "" msgid "Used In" msgstr "" -#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:317 +#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:318 msgid "Tests" msgstr "" @@ -3254,208 +3241,208 @@ msgstr "" msgid "New Variant" msgstr "" -#: part/views.py:80 +#: part/views.py:82 msgid "Add Related Part" msgstr "" -#: part/views.py:136 +#: part/views.py:138 msgid "Delete Related Part" msgstr "" -#: part/views.py:148 +#: part/views.py:150 msgid "Add part attachment" msgstr "" -#: part/views.py:203 templates/attachment_table.html:34 +#: part/views.py:205 templates/attachment_table.html:34 msgid "Edit attachment" msgstr "" -#: part/views.py:209 +#: part/views.py:211 msgid "Part attachment updated" msgstr "" -#: part/views.py:224 +#: part/views.py:226 msgid "Delete Part Attachment" msgstr "" -#: part/views.py:232 +#: part/views.py:234 msgid "Deleted part attachment" msgstr "" -#: part/views.py:241 +#: part/views.py:243 msgid "Create Test Template" msgstr "" -#: part/views.py:270 +#: part/views.py:272 msgid "Edit Test Template" msgstr "" -#: part/views.py:286 +#: part/views.py:288 msgid "Delete Test Template" msgstr "" -#: part/views.py:295 +#: part/views.py:297 msgid "Set Part Category" msgstr "" -#: part/views.py:345 +#: part/views.py:347 #, python-brace-format msgid "Set category for {n} parts" msgstr "" -#: part/views.py:380 +#: part/views.py:382 msgid "Create Variant" msgstr "" -#: part/views.py:462 +#: part/views.py:464 msgid "Duplicate Part" msgstr "" -#: part/views.py:469 +#: part/views.py:471 msgid "Copied part" msgstr "" -#: part/views.py:523 part/views.py:653 +#: part/views.py:525 part/views.py:655 msgid "Possible matches exist - confirm creation of new part" msgstr "" -#: part/views.py:588 templates/js/stock.js:840 +#: part/views.py:590 templates/js/stock.js:840 msgid "Create New Part" msgstr "" -#: part/views.py:595 +#: part/views.py:597 msgid "Created new part" msgstr "" -#: part/views.py:811 +#: part/views.py:813 msgid "Part QR Code" msgstr "" -#: part/views.py:830 +#: part/views.py:832 msgid "Upload Part Image" msgstr "" -#: part/views.py:838 part/views.py:875 +#: part/views.py:840 part/views.py:877 msgid "Updated part image" msgstr "" -#: part/views.py:847 +#: part/views.py:849 msgid "Select Part Image" msgstr "" -#: part/views.py:878 +#: part/views.py:880 msgid "Part image not found" msgstr "" -#: part/views.py:889 +#: part/views.py:891 msgid "Edit Part Properties" msgstr "" -#: part/views.py:916 +#: part/views.py:918 msgid "Duplicate BOM" msgstr "" -#: part/views.py:947 +#: part/views.py:949 msgid "Confirm duplication of BOM from parent" msgstr "" -#: part/views.py:968 +#: part/views.py:970 msgid "Validate BOM" msgstr "" -#: part/views.py:991 +#: part/views.py:993 msgid "Confirm that the BOM is valid" msgstr "" -#: part/views.py:1002 +#: part/views.py:1004 msgid "Validated Bill of Materials" msgstr "" -#: part/views.py:1136 +#: part/views.py:1138 msgid "No BOM file provided" msgstr "" -#: part/views.py:1486 +#: part/views.py:1488 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1511 part/views.py:1514 +#: part/views.py:1513 part/views.py:1516 msgid "Select valid part" msgstr "" -#: part/views.py:1520 +#: part/views.py:1522 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1558 +#: part/views.py:1560 msgid "Select a part" msgstr "" -#: part/views.py:1564 +#: part/views.py:1566 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1568 +#: part/views.py:1570 msgid "Specify quantity" msgstr "" -#: part/views.py:1824 +#: part/views.py:1826 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1833 +#: part/views.py:1835 msgid "Part was deleted" msgstr "" -#: part/views.py:1842 +#: part/views.py:1844 msgid "Part Pricing" msgstr "" -#: part/views.py:1968 +#: part/views.py:1958 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1978 +#: part/views.py:1968 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1987 +#: part/views.py:1977 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1997 +#: part/views.py:1987 msgid "Create Part Parameter" msgstr "" -#: part/views.py:2049 +#: part/views.py:2039 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:2065 +#: part/views.py:2055 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:2124 +#: part/views.py:2114 msgid "Edit Part Category" msgstr "" -#: part/views.py:2161 +#: part/views.py:2151 msgid "Delete Part Category" msgstr "" -#: part/views.py:2169 +#: part/views.py:2159 msgid "Part category was deleted" msgstr "" -#: part/views.py:2232 +#: part/views.py:2222 msgid "Create BOM Item" msgstr "" -#: part/views.py:2300 +#: part/views.py:2290 msgid "Edit BOM item" msgstr "" -#: part/views.py:2350 +#: part/views.py:2340 msgid "Confim BOM item deletion" msgstr "" @@ -3491,291 +3478,295 @@ msgstr "" msgid "Enter unique serial numbers (or leave blank)" msgstr "" -#: stock/forms.py:191 +#: stock/forms.py:192 msgid "Label" msgstr "" -#: stock/forms.py:192 stock/forms.py:248 +#: stock/forms.py:193 stock/forms.py:249 msgid "Select test report template" msgstr "" -#: stock/forms.py:256 +#: stock/forms.py:257 msgid "Include stock items in sub locations" msgstr "" -#: stock/forms.py:291 +#: stock/forms.py:292 msgid "Stock item to install" msgstr "" -#: stock/forms.py:298 +#: stock/forms.py:299 msgid "Stock quantity to assign" msgstr "" -#: stock/forms.py:326 +#: stock/forms.py:327 msgid "Must not exceed available quantity" msgstr "" -#: stock/forms.py:336 +#: stock/forms.py:337 msgid "Destination location for uninstalled items" msgstr "" -#: stock/forms.py:338 +#: stock/forms.py:339 msgid "Add transaction note (optional)" msgstr "" -#: stock/forms.py:340 +#: stock/forms.py:341 msgid "Confirm uninstall" msgstr "" -#: stock/forms.py:340 +#: stock/forms.py:341 msgid "Confirm removal of installed stock items" msgstr "" -#: stock/forms.py:364 +#: stock/forms.py:365 msgid "Destination stock location" msgstr "" -#: stock/forms.py:366 +#: stock/forms.py:367 msgid "Add note (required)" msgstr "" -#: stock/forms.py:370 stock/views.py:916 stock/views.py:1114 +#: stock/forms.py:371 stock/views.py:916 stock/views.py:1114 msgid "Confirm stock adjustment" msgstr "" -#: stock/forms.py:370 +#: stock/forms.py:371 msgid "Confirm movement of stock items" msgstr "" -#: stock/forms.py:372 +#: stock/forms.py:373 msgid "Set Default Location" msgstr "" -#: stock/forms.py:372 +#: stock/forms.py:373 msgid "Set the destination as the default location for selected parts" msgstr "" -#: stock/models.py:178 +#: stock/models.py:179 msgid "Created stock item" msgstr "" -#: stock/models.py:214 +#: stock/models.py:215 msgid "StockItem with this serial number already exists" msgstr "" -#: stock/models.py:250 +#: stock/models.py:251 #, python-brace-format msgid "Part type ('{pf}') must be {pe}" msgstr "" -#: stock/models.py:260 stock/models.py:269 +#: stock/models.py:261 stock/models.py:270 msgid "Quantity must be 1 for item with a serial number" msgstr "" -#: stock/models.py:261 +#: stock/models.py:262 msgid "Serial number cannot be set if quantity greater than 1" msgstr "" -#: stock/models.py:283 +#: stock/models.py:284 msgid "Item cannot belong to itself" msgstr "" -#: stock/models.py:289 +#: stock/models.py:290 msgid "Item must have a build reference if is_building=True" msgstr "" -#: stock/models.py:296 +#: stock/models.py:297 msgid "Build reference does not point to the same part object" msgstr "" -#: stock/models.py:329 +#: stock/models.py:330 msgid "Parent Stock Item" msgstr "" -#: stock/models.py:338 +#: stock/models.py:339 msgid "Base part" msgstr "" -#: stock/models.py:347 +#: stock/models.py:348 msgid "Select a matching supplier part for this stock item" msgstr "" -#: stock/models.py:352 stock/templates/stock/stock_app_base.html:7 +#: stock/models.py:353 stock/templates/stock/stock_app_base.html:7 msgid "Stock Location" msgstr "" -#: stock/models.py:355 +#: stock/models.py:356 msgid "Where is this stock item located?" msgstr "" -#: stock/models.py:360 stock/templates/stock/item_base.html:212 +#: stock/models.py:361 stock/templates/stock/item_base.html:212 msgid "Installed In" msgstr "" -#: stock/models.py:363 +#: stock/models.py:364 msgid "Is this item installed in another item?" msgstr "" -#: stock/models.py:379 +#: stock/models.py:380 msgid "Serial number for this item" msgstr "" -#: stock/models.py:391 +#: stock/models.py:392 msgid "Batch code for this stock item" msgstr "" -#: stock/models.py:395 +#: stock/models.py:396 msgid "Stock Quantity" msgstr "" -#: stock/models.py:404 +#: stock/models.py:405 msgid "Source Build" msgstr "" -#: stock/models.py:406 +#: stock/models.py:407 msgid "Build for this stock item" msgstr "" -#: stock/models.py:417 +#: stock/models.py:418 msgid "Source Purchase Order" msgstr "" -#: stock/models.py:420 +#: stock/models.py:421 msgid "Purchase order for this stock item" msgstr "" -#: stock/models.py:426 +#: stock/models.py:427 msgid "Destination Sales Order" msgstr "" -#: stock/models.py:433 -msgid "Destination Build Order" -msgstr "" - -#: stock/models.py:446 +#: stock/models.py:439 msgid "Delete this Stock Item when stock is depleted" msgstr "" -#: stock/models.py:456 stock/templates/stock/item_notes.html:14 +#: stock/models.py:449 stock/templates/stock/item_notes.html:14 #: stock/templates/stock/item_notes.html:30 msgid "Stock Item Notes" msgstr "" -#: stock/models.py:507 -msgid "Assigned to Customer" +#: stock/models.py:457 stock/templates/stock/item_base.html:266 +msgid "Purchase Price" +msgstr "" + +#: stock/models.py:458 +msgid "Single unit purchase price at time of purchase" msgstr "" #: stock/models.py:509 +msgid "Assigned to Customer" +msgstr "" + +#: stock/models.py:511 msgid "Manually assigned to customer" msgstr "" -#: stock/models.py:522 +#: stock/models.py:524 msgid "Returned from customer" msgstr "" -#: stock/models.py:524 +#: stock/models.py:526 msgid "Returned to location" msgstr "" -#: stock/models.py:652 +#: stock/models.py:651 msgid "Installed into stock item" msgstr "" -#: stock/models.py:660 +#: stock/models.py:659 msgid "Installed stock item" msgstr "" -#: stock/models.py:684 +#: stock/models.py:683 msgid "Uninstalled stock item" msgstr "" -#: stock/models.py:703 +#: stock/models.py:702 msgid "Uninstalled into location" msgstr "" -#: stock/models.py:807 +#: stock/models.py:802 msgid "Part is not set as trackable" msgstr "" -#: stock/models.py:813 +#: stock/models.py:808 msgid "Quantity must be integer" msgstr "" -#: stock/models.py:819 +#: stock/models.py:814 #, python-brace-format msgid "Quantity must not exceed available stock quantity ({n})" msgstr "" -#: stock/models.py:822 +#: stock/models.py:817 msgid "Serial numbers must be a list of integers" msgstr "" -#: stock/models.py:825 +#: stock/models.py:820 msgid "Quantity does not match serial numbers" msgstr "" -#: stock/models.py:857 +#: stock/models.py:852 msgid "Add serial number" msgstr "" -#: stock/models.py:860 +#: stock/models.py:855 #, python-brace-format msgid "Serialized {n} items" msgstr "" -#: stock/models.py:971 +#: stock/models.py:966 msgid "StockItem cannot be moved as it is not in stock" msgstr "" -#: stock/models.py:1316 +#: stock/models.py:1320 msgid "Tracking entry title" msgstr "" -#: stock/models.py:1318 +#: stock/models.py:1322 msgid "Entry notes" msgstr "" -#: stock/models.py:1320 +#: stock/models.py:1324 msgid "Link to external page for further information" msgstr "" -#: stock/models.py:1380 +#: stock/models.py:1384 msgid "Value must be provided for this test" msgstr "" -#: stock/models.py:1386 +#: stock/models.py:1390 msgid "Attachment must be uploaded for this test" msgstr "" -#: stock/models.py:1403 +#: stock/models.py:1407 msgid "Test" msgstr "" -#: stock/models.py:1404 +#: stock/models.py:1408 msgid "Test name" msgstr "" -#: stock/models.py:1409 +#: stock/models.py:1413 msgid "Result" msgstr "" -#: stock/models.py:1410 templates/js/table_filters.js:162 +#: stock/models.py:1414 templates/js/table_filters.js:162 msgid "Test result" msgstr "" -#: stock/models.py:1416 +#: stock/models.py:1420 msgid "Test output value" msgstr "" -#: stock/models.py:1422 +#: stock/models.py:1426 msgid "Attachment" msgstr "" -#: stock/models.py:1423 +#: stock/models.py:1427 msgid "Test result attachment" msgstr "" -#: stock/models.py:1429 +#: stock/models.py:1433 msgid "Test notes" msgstr "" @@ -3905,32 +3896,32 @@ msgstr "" msgid "Stock Item Details" msgstr "" -#: stock/templates/stock/item_base.html:237 templates/js/build.js:426 +#: stock/templates/stock/item_base.html:231 templates/js/build.js:426 msgid "No location set" msgstr "" -#: stock/templates/stock/item_base.html:244 +#: stock/templates/stock/item_base.html:238 msgid "Barcode Identifier" msgstr "" -#: stock/templates/stock/item_base.html:258 templates/js/build.js:626 +#: stock/templates/stock/item_base.html:252 templates/js/build.js:626 #: templates/navbar.html:25 msgid "Build" msgstr "" -#: stock/templates/stock/item_base.html:272 +#: stock/templates/stock/item_base.html:273 msgid "Parent Item" msgstr "" -#: stock/templates/stock/item_base.html:297 +#: stock/templates/stock/item_base.html:298 msgid "Last Updated" msgstr "" -#: stock/templates/stock/item_base.html:302 +#: stock/templates/stock/item_base.html:303 msgid "Last Stocktake" msgstr "" -#: stock/templates/stock/item_base.html:306 +#: stock/templates/stock/item_base.html:307 msgid "No stocktake performed" msgstr "" @@ -4369,18 +4360,6 @@ msgstr "" msgid "Build Order Settings" msgstr "" -#: templates/InvenTree/settings/currency.html:5 -msgid "General Settings" -msgstr "" - -#: templates/InvenTree/settings/currency.html:14 -msgid "Currencies" -msgstr "" - -#: templates/InvenTree/settings/currency.html:18 -msgid "New Currency" -msgstr "" - #: templates/InvenTree/settings/global.html:10 msgid "Global InvenTree Settings" msgstr "" @@ -4393,19 +4372,19 @@ msgstr "" msgid "Part Options" msgstr "" -#: templates/InvenTree/settings/part.html:31 +#: templates/InvenTree/settings/part.html:33 msgid "Part Parameter Templates" msgstr "" -#: templates/InvenTree/settings/part.html:52 +#: templates/InvenTree/settings/part.html:54 msgid "No part parameter templates found" msgstr "" -#: templates/InvenTree/settings/part.html:72 +#: templates/InvenTree/settings/part.html:74 msgid "Edit Template" msgstr "" -#: templates/InvenTree/settings/part.html:73 +#: templates/InvenTree/settings/part.html:75 msgid "Delete Template" msgstr "" @@ -4455,10 +4434,6 @@ msgstr "" msgid "Global" msgstr "" -#: templates/InvenTree/settings/tabs.html:19 -msgid "Currency" -msgstr "" - #: templates/InvenTree/settings/theme.html:10 msgid "Theme Settings" msgstr "" @@ -5188,38 +5163,38 @@ msgstr "" msgid "Important dates" msgstr "" -#: users/models.py:130 +#: users/models.py:131 msgid "Permission set" msgstr "" -#: users/models.py:138 +#: users/models.py:139 msgid "Group" msgstr "" -#: users/models.py:141 +#: users/models.py:142 msgid "View" msgstr "" -#: users/models.py:141 +#: users/models.py:142 msgid "Permission to view items" msgstr "" -#: users/models.py:143 +#: users/models.py:144 msgid "Add" msgstr "" -#: users/models.py:143 +#: users/models.py:144 msgid "Permission to add items" msgstr "" -#: users/models.py:145 +#: users/models.py:146 msgid "Change" msgstr "" -#: users/models.py:145 +#: users/models.py:146 msgid "Permissions to edit items" msgstr "" -#: users/models.py:147 +#: users/models.py:148 msgid "Permission to delete items" msgstr "" diff --git a/InvenTree/locale/es/LC_MESSAGES/django.po b/InvenTree/locale/es/LC_MESSAGES/django.po index c4c14849c2..976e28c2c6 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-11-09 12:47+0000\n" +"POT-Creation-Date: 2020-11-10 13:31+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -26,23 +26,23 @@ msgstr "" msgid "No matching action found" msgstr "" -#: InvenTree/forms.py:130 build/forms.py:82 build/forms.py:170 +#: InvenTree/forms.py:107 build/forms.py:82 build/forms.py:170 msgid "Confirm" msgstr "" -#: InvenTree/forms.py:146 +#: InvenTree/forms.py:123 msgid "Confirm item deletion" msgstr "" -#: InvenTree/forms.py:178 +#: InvenTree/forms.py:155 msgid "Enter new password" msgstr "" -#: InvenTree/forms.py:185 +#: InvenTree/forms.py:162 msgid "Confirm new password" msgstr "" -#: InvenTree/forms.py:220 +#: InvenTree/forms.py:197 msgid "Apply Theme" msgstr "" @@ -99,19 +99,19 @@ msgstr "" msgid "Description (optional)" msgstr "" -#: InvenTree/settings.py:348 +#: InvenTree/settings.py:350 msgid "English" msgstr "" -#: InvenTree/settings.py:349 +#: InvenTree/settings.py:351 msgid "German" msgstr "" -#: InvenTree/settings.py:350 +#: InvenTree/settings.py:352 msgid "French" msgstr "" -#: InvenTree/settings.py:351 +#: InvenTree/settings.py:353 msgid "Polish" msgstr "" @@ -280,7 +280,7 @@ msgstr "" #: order/templates/order/sales_order_detail.html:156 #: part/templates/part/allocation.html:16 #: part/templates/part/allocation.html:49 -#: part/templates/part/sale_prices.html:80 stock/forms.py:297 +#: part/templates/part/sale_prices.html:82 stock/forms.py:298 #: stock/templates/stock/item_base.html:40 #: stock/templates/stock/item_base.html:46 #: stock/templates/stock/item_base.html:197 @@ -345,14 +345,13 @@ msgstr "" #: build/models.py:56 build/templates/build/build_base.html:8 #: build/templates/build/build_base.html:35 #: part/templates/part/allocation.html:20 -#: stock/templates/stock/item_base.html:227 msgid "Build Order" msgstr "" #: build/models.py:57 build/templates/build/index.html:6 #: build/templates/build/index.html:14 order/templates/order/so_builds.html:11 #: order/templates/order/so_tabs.html:9 part/templates/part/tabs.html:31 -#: templates/InvenTree/settings/tabs.html:28 users/models.py:30 +#: templates/InvenTree/settings/tabs.html:25 users/models.py:30 msgid "Build Orders" msgstr "" @@ -460,7 +459,7 @@ msgstr "" msgid "Build status code" msgstr "" -#: build/models.py:157 stock/models.py:389 +#: build/models.py:157 stock/models.py:390 msgid "Batch Code" msgstr "" @@ -472,11 +471,11 @@ msgstr "" #: company/templates/company/supplier_part_base.html:68 #: company/templates/company/supplier_part_detail.html:24 #: part/templates/part/detail.html:80 part/templates/part/part_base.html:102 -#: stock/models.py:383 stock/templates/stock/item_base.html:279 +#: stock/models.py:384 stock/templates/stock/item_base.html:280 msgid "External Link" msgstr "" -#: build/models.py:177 part/models.py:597 stock/models.py:385 +#: build/models.py:177 part/models.py:609 stock/models.py:386 msgid "Link to external URL" msgstr "" @@ -484,8 +483,8 @@ msgstr "" #: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:18 #: order/templates/order/purchase_order_detail.html:203 #: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:73 -#: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:455 -#: stock/models.py:1428 stock/templates/stock/tabs.html:26 +#: stock/forms.py:307 stock/forms.py:339 stock/forms.py:367 stock/models.py:448 +#: stock/models.py:1432 stock/templates/stock/tabs.html:26 #: templates/js/barcode.js:391 templates/js/bom.js:250 #: templates/js/stock.js:116 templates/js/stock.js:578 msgid "Notes" @@ -549,11 +548,11 @@ msgstr "" msgid "Source stock item" msgstr "" -#: build/models.py:976 +#: build/models.py:975 msgid "Stock quantity to allocate to build" msgstr "" -#: build/models.py:984 +#: build/models.py:983 msgid "Destination stock item" msgstr "" @@ -618,8 +617,8 @@ msgid "" "The following stock items will be allocated to the specified build output" msgstr "" -#: build/templates/build/auto_allocate.html:18 stock/forms.py:336 -#: stock/templates/stock/item_base.html:233 +#: build/templates/build/auto_allocate.html:18 stock/forms.py:337 +#: stock/templates/stock/item_base.html:227 #: stock/templates/stock/stock_adjust.html:17 #: templates/InvenTree/search.html:183 templates/js/barcode.js:337 #: templates/js/build.js:418 templates/js/stock.js:570 @@ -675,7 +674,7 @@ msgstr "" #: build/templates/build/build_base.html:83 #: build/templates/build/detail.html:57 #: order/templates/order/receive_parts.html:24 -#: stock/templates/stock/item_base.html:311 templates/InvenTree/search.html:175 +#: stock/templates/stock/item_base.html:312 templates/InvenTree/search.html:175 #: templates/js/barcode.js:42 templates/js/build.js:675 #: templates/js/order.js:172 templates/js/order.js:254 #: templates/js/stock.js:557 templates/js/stock.js:961 @@ -786,7 +785,7 @@ msgstr "" msgid "Stock can be taken from any available location." msgstr "" -#: build/templates/build/detail.html:44 stock/forms.py:364 +#: build/templates/build/detail.html:44 stock/forms.py:365 msgid "Destination" msgstr "" @@ -795,7 +794,7 @@ msgid "Destination location not specified" msgstr "" #: build/templates/build/detail.html:68 -#: stock/templates/stock/item_base.html:251 templates/js/stock.js:565 +#: stock/templates/stock/item_base.html:245 templates/js/stock.js:565 #: templates/js/stock.js:968 templates/js/table_filters.js:80 #: templates/js/table_filters.js:151 msgid "Batch" @@ -887,7 +886,7 @@ msgstr "" msgid "Create Build Output" msgstr "" -#: build/views.py:207 stock/models.py:832 stock/views.py:1647 +#: build/views.py:207 stock/models.py:827 stock/views.py:1647 msgid "Serial numbers already exist" msgstr "" @@ -992,7 +991,7 @@ msgstr "" msgid "Add Build Order Attachment" msgstr "" -#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:164 +#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:166 #: stock/views.py:176 msgid "Added attachment" msgstr "" @@ -1013,158 +1012,163 @@ msgstr "" msgid "Deleted attachment" msgstr "" -#: common/models.py:51 +#: common/models.py:55 msgid "InvenTree Instance Name" msgstr "" -#: common/models.py:53 +#: common/models.py:57 msgid "String descriptor for the server instance" msgstr "" -#: common/models.py:57 company/models.py:89 company/models.py:90 +#: common/models.py:61 company/models.py:89 company/models.py:90 msgid "Company name" msgstr "" -#: common/models.py:58 +#: common/models.py:62 msgid "Internal company name" msgstr "" -#: common/models.py:63 -msgid "IPN Regex" -msgstr "" - -#: common/models.py:64 -msgid "Regular expression pattern for matching Part IPN" +#: common/models.py:67 +msgid "Default Currency" msgstr "" #: common/models.py:68 -msgid "Copy Part BOM Data" +msgid "Default currency" msgstr "" -#: common/models.py:69 -msgid "Copy BOM data by default when duplicating a part" +#: common/models.py:74 +msgid "IPN Regex" msgstr "" #: common/models.py:75 +msgid "Regular expression pattern for matching Part IPN" +msgstr "" + +#: common/models.py:79 +msgid "Allow Duplicate IPN" +msgstr "" + +#: common/models.py:80 +msgid "Allow multiple parts to share the same IPN" +msgstr "" + +#: common/models.py:86 +msgid "Copy Part BOM Data" +msgstr "" + +#: common/models.py:87 +msgid "Copy BOM data by default when duplicating a part" +msgstr "" + +#: common/models.py:93 msgid "Copy Part Parameter Data" msgstr "" -#: common/models.py:76 +#: common/models.py:94 msgid "Copy parameter data by default when duplicating a part" msgstr "" -#: common/models.py:82 +#: common/models.py:100 msgid "Copy Part Test Data" msgstr "" -#: common/models.py:83 +#: common/models.py:101 msgid "Copy test data by default when duplicating a part" msgstr "" -#: common/models.py:89 part/models.py:668 part/templates/part/detail.html:168 +#: common/models.py:107 part/models.py:680 part/templates/part/detail.html:168 #: templates/js/table_filters.js:264 msgid "Component" msgstr "" -#: common/models.py:90 +#: common/models.py:108 msgid "Parts can be used as sub-components by default" msgstr "" -#: common/models.py:96 part/models.py:679 part/templates/part/detail.html:188 +#: common/models.py:114 part/models.py:691 part/templates/part/detail.html:188 msgid "Purchaseable" msgstr "" -#: common/models.py:97 +#: common/models.py:115 msgid "Parts are purchaseable by default" msgstr "" -#: common/models.py:103 part/models.py:684 part/templates/part/detail.html:198 +#: common/models.py:121 part/models.py:696 part/templates/part/detail.html:198 #: templates/js/table_filters.js:272 msgid "Salable" msgstr "" -#: common/models.py:104 +#: common/models.py:122 msgid "Parts are salable by default" msgstr "" -#: common/models.py:110 part/models.py:674 part/templates/part/detail.html:178 +#: common/models.py:128 part/models.py:686 part/templates/part/detail.html:178 #: templates/js/table_filters.js:31 templates/js/table_filters.js:276 msgid "Trackable" msgstr "" -#: common/models.py:111 +#: common/models.py:129 msgid "Parts are trackable by default" msgstr "" -#: common/models.py:117 +#: common/models.py:135 msgid "Build Order Reference Prefix" msgstr "" -#: common/models.py:118 +#: common/models.py:136 msgid "Prefix value for build order reference" msgstr "" -#: common/models.py:123 +#: common/models.py:141 msgid "Build Order Reference Regex" msgstr "" -#: common/models.py:124 +#: common/models.py:142 msgid "Regular expression pattern for matching build order reference" msgstr "" -#: common/models.py:128 +#: common/models.py:146 msgid "Sales Order Reference Prefix" msgstr "" -#: common/models.py:129 +#: common/models.py:147 msgid "Prefix value for sales order reference" msgstr "" -#: common/models.py:133 +#: common/models.py:151 msgid "Purchase Order Reference Prefix" msgstr "" -#: common/models.py:134 +#: common/models.py:152 msgid "Prefix value for purchase order reference" msgstr "" -#: common/models.py:312 +#: common/models.py:357 msgid "Settings key (must be unique - case insensitive" msgstr "" -#: common/models.py:314 +#: common/models.py:359 msgid "Settings value" msgstr "" -#: common/models.py:366 +#: common/models.py:415 msgid "Value must be a boolean value" msgstr "" -#: common/models.py:380 +#: common/models.py:429 msgid "Key string must be unique" msgstr "" -#: common/models.py:419 -msgid "Currency Symbol e.g. $" +#: common/models.py:474 company/templates/company/supplier_part_pricing.html:80 +#: part/templates/part/sale_prices.html:87 templates/js/bom.js:234 +msgid "Price" msgstr "" -#: common/models.py:421 -msgid "Currency Suffix e.g. AUD" +#: common/models.py:475 +msgid "Unit price at specified quantity" msgstr "" -#: common/models.py:423 -msgid "Currency Description" -msgstr "" - -#: common/models.py:425 -msgid "Currency Value" -msgstr "" - -#: common/models.py:427 -msgid "Use this currency as the base currency" -msgstr "" - -#: common/models.py:510 +#: common/models.py:498 msgid "Default" msgstr "" @@ -1172,19 +1176,7 @@ msgstr "" msgid "Current value" msgstr "" -#: common/views.py:23 -msgid "Create new Currency" -msgstr "" - -#: common/views.py:31 -msgid "Edit Currency" -msgstr "" - -#: common/views.py:38 -msgid "Delete Currency" -msgstr "" - -#: common/views.py:49 +#: common/views.py:25 msgid "Change Setting" msgstr "" @@ -1253,7 +1245,7 @@ msgstr "" msgid "Does this company manufacture parts?" msgstr "" -#: company/models.py:283 stock/models.py:337 +#: company/models.py:283 stock/models.py:338 #: stock/templates/stock/item_base.html:177 msgid "Base Part" msgstr "" @@ -1325,14 +1317,14 @@ msgstr "" #: company/templates/company/supplier_part_detail.html:21 #: order/templates/order/order_base.html:79 #: order/templates/order/order_wizard/select_pos.html:30 part/bom.py:170 -#: stock/templates/stock/item_base.html:286 templates/js/company.js:48 +#: stock/templates/stock/item_base.html:287 templates/js/company.js:48 #: templates/js/company.js:164 templates/js/order.js:154 msgid "Supplier" msgstr "" #: company/templates/company/detail.html:26 -#: order/templates/order/sales_order_base.html:81 stock/models.py:372 -#: stock/models.py:373 stock/templates/stock/item_base.html:204 +#: order/templates/order/sales_order_base.html:81 stock/models.py:373 +#: stock/models.py:374 stock/templates/stock/item_base.html:204 #: templates/js/company.js:40 templates/js/order.js:236 msgid "Customer" msgstr "" @@ -1380,21 +1372,21 @@ msgstr "" msgid "Create new Part" msgstr "" -#: company/templates/company/detail_part.html:69 company/views.py:53 +#: company/templates/company/detail_part.html:69 company/views.py:55 #: part/templates/part/supplier.html:47 msgid "New Supplier" msgstr "" -#: company/templates/company/detail_part.html:70 company/views.py:192 +#: company/templates/company/detail_part.html:70 company/views.py:194 msgid "Create new Supplier" msgstr "" -#: company/templates/company/detail_part.html:75 company/views.py:60 +#: company/templates/company/detail_part.html:75 company/views.py:62 #: part/templates/part/supplier.html:53 msgid "New Manufacturer" msgstr "" -#: company/templates/company/detail_part.html:76 company/views.py:195 +#: company/templates/company/detail_part.html:76 company/views.py:197 msgid "Create new Manufacturer" msgstr "" @@ -1428,7 +1420,7 @@ msgstr "" #: order/templates/order/purchase_orders.html:7 #: order/templates/order/purchase_orders.html:12 #: part/templates/part/orders.html:9 part/templates/part/tabs.html:48 -#: templates/InvenTree/settings/tabs.html:31 templates/navbar.html:33 +#: templates/InvenTree/settings/tabs.html:28 templates/navbar.html:33 #: users/models.py:31 msgid "Purchase Orders" msgstr "" @@ -1448,7 +1440,7 @@ msgstr "" #: order/templates/order/sales_orders.html:7 #: order/templates/order/sales_orders.html:12 #: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:56 -#: templates/InvenTree/settings/tabs.html:34 templates/navbar.html:42 +#: templates/InvenTree/settings/tabs.html:31 templates/navbar.html:42 #: users/models.py:32 msgid "Sales Orders" msgstr "" @@ -1464,8 +1456,8 @@ msgid "New Sales Order" msgstr "" #: company/templates/company/supplier_part_base.html:6 -#: company/templates/company/supplier_part_base.html:19 stock/models.py:346 -#: stock/templates/stock/item_base.html:291 templates/js/company.js:180 +#: company/templates/company/supplier_part_base.html:19 stock/models.py:347 +#: stock/templates/stock/item_base.html:292 templates/js/company.js:180 msgid "Supplier Part" msgstr "" @@ -1521,28 +1513,23 @@ msgstr "" msgid "Pricing Information" msgstr "" -#: company/templates/company/supplier_part_pricing.html:17 company/views.py:410 -#: part/templates/part/sale_prices.html:13 part/views.py:2360 +#: company/templates/company/supplier_part_pricing.html:17 company/views.py:412 +#: part/templates/part/sale_prices.html:14 part/views.py:2350 msgid "Add Price Break" msgstr "" #: company/templates/company/supplier_part_pricing.html:36 -#: part/templates/part/sale_prices.html:41 +#: part/templates/part/sale_prices.html:43 msgid "No price break information found" msgstr "" -#: company/templates/company/supplier_part_pricing.html:80 -#: part/templates/part/sale_prices.html:85 templates/js/bom.js:234 -msgid "Price" -msgstr "" - -#: company/templates/company/supplier_part_pricing.html:94 -#: part/templates/part/sale_prices.html:99 +#: company/templates/company/supplier_part_pricing.html:87 +#: part/templates/part/sale_prices.html:94 msgid "Edit price break" msgstr "" -#: company/templates/company/supplier_part_pricing.html:95 -#: part/templates/part/sale_prices.html:100 +#: company/templates/company/supplier_part_pricing.html:88 +#: part/templates/part/sale_prices.html:95 msgid "Delete price break" msgstr "" @@ -1557,7 +1544,7 @@ msgstr "" #: company/templates/company/supplier_part_tabs.html:8 #: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18 #: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155 -#: templates/InvenTree/settings/tabs.html:25 templates/js/part.js:192 +#: templates/InvenTree/settings/tabs.html:22 templates/js/part.js:192 #: templates/js/part.js:418 templates/js/stock.js:502 templates/navbar.html:22 #: users/models.py:29 msgid "Stock" @@ -1571,93 +1558,93 @@ msgstr "" #: order/templates/order/receive_parts.html:14 part/models.py:295 #: part/templates/part/cat_link.html:7 part/templates/part/category.html:94 #: part/templates/part/category_tabs.html:6 -#: templates/InvenTree/settings/tabs.html:22 templates/navbar.html:19 +#: templates/InvenTree/settings/tabs.html:19 templates/navbar.html:19 #: templates/stats.html:8 templates/stats.html:17 users/models.py:28 msgid "Parts" msgstr "" -#: company/views.py:52 part/templates/part/tabs.html:42 +#: company/views.py:54 part/templates/part/tabs.html:42 #: templates/navbar.html:31 msgid "Suppliers" msgstr "" -#: company/views.py:59 templates/navbar.html:32 +#: company/views.py:61 templates/navbar.html:32 msgid "Manufacturers" msgstr "" -#: company/views.py:66 templates/navbar.html:41 +#: company/views.py:68 templates/navbar.html:41 msgid "Customers" msgstr "" -#: company/views.py:67 +#: company/views.py:69 msgid "New Customer" msgstr "" -#: company/views.py:75 +#: company/views.py:77 msgid "Companies" msgstr "" -#: company/views.py:76 +#: company/views.py:78 msgid "New Company" msgstr "" -#: company/views.py:154 +#: company/views.py:156 msgid "Update Company Image" msgstr "" -#: company/views.py:160 +#: company/views.py:162 msgid "Updated company image" msgstr "" -#: company/views.py:170 +#: company/views.py:172 msgid "Edit Company" msgstr "" -#: company/views.py:175 +#: company/views.py:177 msgid "Edited company information" msgstr "" -#: company/views.py:198 +#: company/views.py:200 msgid "Create new Customer" msgstr "" -#: company/views.py:200 +#: company/views.py:202 msgid "Create new Company" msgstr "" -#: company/views.py:227 +#: company/views.py:229 msgid "Created new company" msgstr "" -#: company/views.py:237 +#: company/views.py:239 msgid "Delete Company" msgstr "" -#: company/views.py:243 +#: company/views.py:245 msgid "Company was deleted" msgstr "" -#: company/views.py:268 +#: company/views.py:270 msgid "Edit Supplier Part" msgstr "" -#: company/views.py:278 templates/js/stock.js:846 +#: company/views.py:280 templates/js/stock.js:846 msgid "Create new Supplier Part" msgstr "" -#: company/views.py:339 +#: company/views.py:341 msgid "Delete Supplier Part" msgstr "" -#: company/views.py:416 part/views.py:2366 +#: company/views.py:418 part/views.py:2356 msgid "Added new price break" msgstr "" -#: company/views.py:453 part/views.py:2411 +#: company/views.py:454 part/views.py:2400 msgid "Edit Price Break" msgstr "" -#: company/views.py:469 part/views.py:2427 +#: company/views.py:470 part/views.py:2416 msgid "Delete Price Break" msgstr "" @@ -1750,8 +1737,8 @@ msgstr "" msgid "Date order was completed" msgstr "" -#: order/models.py:185 order/models.py:267 part/views.py:1477 -#: stock/models.py:243 stock/models.py:816 +#: order/models.py:185 order/models.py:267 part/views.py:1479 +#: stock/models.py:244 stock/models.py:811 msgid "Quantity must be greater than zero" msgstr "" @@ -1789,7 +1776,7 @@ msgstr "" #: order/models.py:484 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 -#: stock/templates/stock/item_base.html:265 templates/js/order.js:139 +#: stock/templates/stock/item_base.html:259 templates/js/order.js:139 msgid "Purchase Order" msgstr "" @@ -1817,11 +1804,11 @@ msgstr "" msgid "Quantity must be 1 for serialized stock item" msgstr "" -#: order/models.py:626 +#: order/models.py:625 msgid "Select stock item to allocate" msgstr "" -#: order/models.py:629 +#: order/models.py:628 msgid "Enter stock allocation quantity" msgstr "" @@ -2032,7 +2019,7 @@ msgid "Sales Order Items" msgstr "" #: order/templates/order/sales_order_detail.html:72 -#: order/templates/order/sales_order_detail.html:154 stock/models.py:377 +#: order/templates/order/sales_order_detail.html:154 stock/models.py:378 #: stock/templates/stock/item_base.html:191 templates/js/build.js:402 msgid "Serial Number" msgstr "" @@ -2269,114 +2256,110 @@ msgstr "" msgid "Error reading BOM file (incorrect row size)" msgstr "" -#: part/forms.py:62 stock/forms.py:254 +#: part/forms.py:60 stock/forms.py:255 msgid "File Format" msgstr "" -#: part/forms.py:62 stock/forms.py:254 +#: part/forms.py:60 stock/forms.py:255 msgid "Select output file format" msgstr "" -#: part/forms.py:64 +#: part/forms.py:62 msgid "Cascading" msgstr "" -#: part/forms.py:64 +#: part/forms.py:62 msgid "Download cascading / multi-level BOM" msgstr "" -#: part/forms.py:66 +#: part/forms.py:64 msgid "Levels" msgstr "" -#: part/forms.py:66 +#: part/forms.py:64 msgid "Select maximum number of BOM levels to export (0 = all levels)" msgstr "" -#: part/forms.py:68 +#: part/forms.py:66 msgid "Include Parameter Data" msgstr "" -#: part/forms.py:68 +#: part/forms.py:66 msgid "Include part parameters data in exported BOM" msgstr "" -#: part/forms.py:70 +#: part/forms.py:68 msgid "Include Stock Data" msgstr "" -#: part/forms.py:70 +#: part/forms.py:68 msgid "Include part stock data in exported BOM" msgstr "" -#: part/forms.py:72 +#: part/forms.py:70 msgid "Include Supplier Data" msgstr "" -#: part/forms.py:72 +#: part/forms.py:70 msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:93 part/models.py:1632 +#: part/forms.py:91 part/models.py:1644 msgid "Parent Part" msgstr "" -#: part/forms.py:94 part/templates/part/bom_duplicate.html:7 +#: part/forms.py:92 part/templates/part/bom_duplicate.html:7 msgid "Select parent part to copy BOM from" msgstr "" -#: part/forms.py:100 +#: part/forms.py:98 msgid "Clear existing BOM items" msgstr "" -#: part/forms.py:105 +#: part/forms.py:103 msgid "Confirm BOM duplication" msgstr "" -#: part/forms.py:123 +#: part/forms.py:121 msgid "Confirm that the BOM is correct" msgstr "" -#: part/forms.py:135 +#: part/forms.py:133 msgid "Select BOM file to upload" msgstr "" -#: part/forms.py:154 +#: part/forms.py:152 msgid "Related Part" msgstr "" -#: part/forms.py:173 +#: part/forms.py:171 msgid "Select part category" msgstr "" -#: part/forms.py:189 +#: part/forms.py:187 msgid "Duplicate all BOM data for this part" msgstr "" -#: part/forms.py:190 +#: part/forms.py:188 msgid "Copy BOM" msgstr "" -#: part/forms.py:195 +#: part/forms.py:193 msgid "Duplicate all parameter data for this part" msgstr "" -#: part/forms.py:196 +#: part/forms.py:194 msgid "Copy Parameters" msgstr "" -#: part/forms.py:201 +#: part/forms.py:199 msgid "Confirm part creation" msgstr "" -#: part/forms.py:298 +#: part/forms.py:296 msgid "Input quantity for price calculation" msgstr "" -#: part/forms.py:301 -msgid "Select currency for price calculation" -msgstr "" - #: part/models.py:67 msgid "Default location for parts in this category" msgstr "" @@ -2411,225 +2394,229 @@ msgstr "" msgid "Most recent serial number is" msgstr "" -#: part/models.py:540 +#: part/models.py:541 +msgid "Duplicate IPN not allowed in part settings" +msgstr "" + +#: part/models.py:552 msgid "Part must be unique for name, IPN and revision" msgstr "" -#: part/models.py:569 part/templates/part/detail.html:19 +#: part/models.py:581 part/templates/part/detail.html:19 msgid "Part name" msgstr "" -#: part/models.py:573 +#: part/models.py:585 msgid "Is this part a template part?" msgstr "" -#: part/models.py:582 +#: part/models.py:594 msgid "Is this part a variant of another part?" msgstr "" -#: part/models.py:584 +#: part/models.py:596 msgid "Part description" msgstr "" -#: part/models.py:586 +#: part/models.py:598 msgid "Part keywords to improve visibility in search results" msgstr "" -#: part/models.py:591 +#: part/models.py:603 msgid "Part category" msgstr "" -#: part/models.py:593 +#: part/models.py:605 msgid "Internal Part Number" msgstr "" -#: part/models.py:595 +#: part/models.py:607 msgid "Part revision or version number" msgstr "" -#: part/models.py:609 +#: part/models.py:621 msgid "Where is this item normally stored?" msgstr "" -#: part/models.py:653 +#: part/models.py:665 msgid "Default supplier part" msgstr "" -#: part/models.py:656 +#: part/models.py:668 msgid "Minimum allowed stock level" msgstr "" -#: part/models.py:658 +#: part/models.py:670 msgid "Stock keeping units for this part" msgstr "" -#: part/models.py:662 part/templates/part/detail.html:158 +#: part/models.py:674 part/templates/part/detail.html:158 #: templates/js/table_filters.js:260 msgid "Assembly" msgstr "" -#: part/models.py:663 +#: part/models.py:675 msgid "Can this part be built from other parts?" msgstr "" -#: part/models.py:669 +#: part/models.py:681 msgid "Can this part be used to build other parts?" msgstr "" -#: part/models.py:675 +#: part/models.py:687 msgid "Does this part have tracking for unique items?" msgstr "" -#: part/models.py:680 +#: part/models.py:692 msgid "Can this part be purchased from external suppliers?" msgstr "" -#: part/models.py:685 +#: part/models.py:697 msgid "Can this part be sold to customers?" msgstr "" -#: part/models.py:689 part/templates/part/detail.html:215 +#: part/models.py:701 part/templates/part/detail.html:215 #: templates/js/table_filters.js:19 templates/js/table_filters.js:55 #: templates/js/table_filters.js:186 templates/js/table_filters.js:243 msgid "Active" msgstr "" -#: part/models.py:690 +#: part/models.py:702 msgid "Is this part active?" msgstr "" -#: part/models.py:694 part/templates/part/detail.html:138 +#: part/models.py:706 part/templates/part/detail.html:138 #: templates/js/table_filters.js:27 msgid "Virtual" msgstr "" -#: part/models.py:695 +#: part/models.py:707 msgid "Is this a virtual part, such as a software product or license?" msgstr "" -#: part/models.py:697 +#: part/models.py:709 msgid "Part notes - supports Markdown formatting" msgstr "" -#: part/models.py:699 +#: part/models.py:711 msgid "Stored BOM checksum" msgstr "" -#: part/models.py:1505 +#: part/models.py:1517 msgid "Test templates can only be created for trackable parts" msgstr "" -#: part/models.py:1522 +#: part/models.py:1534 msgid "Test with this name already exists for this part" msgstr "" -#: part/models.py:1541 templates/js/part.js:567 templates/js/stock.js:92 +#: part/models.py:1553 templates/js/part.js:567 templates/js/stock.js:92 msgid "Test Name" msgstr "" -#: part/models.py:1542 +#: part/models.py:1554 msgid "Enter a name for the test" msgstr "" -#: part/models.py:1547 +#: part/models.py:1559 msgid "Test Description" msgstr "" -#: part/models.py:1548 +#: part/models.py:1560 msgid "Enter description for this test" msgstr "" -#: part/models.py:1553 templates/js/part.js:576 +#: part/models.py:1565 templates/js/part.js:576 #: templates/js/table_filters.js:172 msgid "Required" msgstr "" -#: part/models.py:1554 +#: part/models.py:1566 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1559 templates/js/part.js:584 +#: part/models.py:1571 templates/js/part.js:584 msgid "Requires Value" msgstr "" -#: part/models.py:1560 +#: part/models.py:1572 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1565 templates/js/part.js:591 +#: part/models.py:1577 templates/js/part.js:591 msgid "Requires Attachment" msgstr "" -#: part/models.py:1566 +#: part/models.py:1578 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1599 +#: part/models.py:1611 msgid "Parameter template name must be unique" msgstr "" -#: part/models.py:1604 +#: part/models.py:1616 msgid "Parameter Name" msgstr "" -#: part/models.py:1606 +#: part/models.py:1618 msgid "Parameter Units" msgstr "" -#: part/models.py:1634 +#: part/models.py:1646 msgid "Parameter Template" msgstr "" -#: part/models.py:1636 +#: part/models.py:1648 msgid "Parameter Value" msgstr "" -#: part/models.py:1673 +#: part/models.py:1685 msgid "Select parent part" msgstr "" -#: part/models.py:1681 +#: part/models.py:1693 msgid "Select part to be used in BOM" msgstr "" -#: part/models.py:1687 +#: part/models.py:1699 msgid "BOM quantity for this BOM item" msgstr "" -#: part/models.py:1689 +#: part/models.py:1701 msgid "This BOM item is optional" msgstr "" -#: part/models.py:1692 +#: part/models.py:1704 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "" -#: part/models.py:1695 +#: part/models.py:1707 msgid "BOM item reference" msgstr "" -#: part/models.py:1698 +#: part/models.py:1710 msgid "BOM item notes" msgstr "" -#: part/models.py:1700 +#: part/models.py:1712 msgid "BOM line checksum" msgstr "" -#: part/models.py:1767 part/views.py:1483 part/views.py:1535 -#: stock/models.py:233 +#: part/models.py:1779 part/views.py:1485 part/views.py:1537 +#: stock/models.py:234 msgid "Quantity must be integer value for trackable parts" msgstr "" -#: part/models.py:1783 +#: part/models.py:1795 msgid "BOM Item" msgstr "" -#: part/models.py:1898 +#: part/models.py:1910 msgid "Select Related Part" msgstr "" -#: part/models.py:1930 +#: part/models.py:1942 msgid "" "Error creating relationship: check that the part is not related to itself " "and that the relationship is unique" @@ -2650,7 +2637,7 @@ msgstr "" #: part/templates/part/allocation.html:45 #: stock/templates/stock/item_base.html:8 #: stock/templates/stock/item_base.html:72 -#: stock/templates/stock/item_base.html:273 +#: stock/templates/stock/item_base.html:274 #: stock/templates/stock/stock_adjust.html:16 templates/js/build.js:724 #: templates/js/stock.js:695 templates/js/stock.js:944 msgid "Stock Item" @@ -2717,7 +2704,7 @@ msgstr "" msgid "Validate" msgstr "" -#: part/templates/part/bom.html:62 part/views.py:1774 +#: part/templates/part/bom.html:62 part/views.py:1776 msgid "Export Bill of Materials" msgstr "" @@ -2813,7 +2800,7 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:24 part/views.py:2177 +#: part/templates/part/category.html:24 part/views.py:2167 msgid "Create new part category" msgstr "" @@ -2967,7 +2954,7 @@ msgstr "" msgid "Part is not a virtual part" msgstr "" -#: part/templates/part/detail.html:148 stock/forms.py:248 +#: part/templates/part/detail.html:148 stock/forms.py:249 #: templates/js/table_filters.js:23 templates/js/table_filters.js:248 msgid "Template" msgstr "" @@ -3036,17 +3023,17 @@ msgstr "" msgid "Add new parameter" msgstr "" -#: part/templates/part/params.html:15 templates/InvenTree/settings/part.html:35 +#: part/templates/part/params.html:15 templates/InvenTree/settings/part.html:37 msgid "New Parameter" msgstr "" -#: part/templates/part/params.html:25 stock/models.py:1415 +#: part/templates/part/params.html:25 stock/models.py:1419 #: templates/js/stock.js:112 msgid "Value" msgstr "" #: part/templates/part/params.html:41 part/templates/part/related.html:41 -#: part/templates/part/supplier.html:19 users/models.py:147 +#: part/templates/part/supplier.html:19 users/models.py:148 msgid "Delete" msgstr "" @@ -3226,7 +3213,7 @@ msgstr "" msgid "Used In" msgstr "" -#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:317 +#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:318 msgid "Tests" msgstr "" @@ -3254,208 +3241,208 @@ msgstr "" msgid "New Variant" msgstr "" -#: part/views.py:80 +#: part/views.py:82 msgid "Add Related Part" msgstr "" -#: part/views.py:136 +#: part/views.py:138 msgid "Delete Related Part" msgstr "" -#: part/views.py:148 +#: part/views.py:150 msgid "Add part attachment" msgstr "" -#: part/views.py:203 templates/attachment_table.html:34 +#: part/views.py:205 templates/attachment_table.html:34 msgid "Edit attachment" msgstr "" -#: part/views.py:209 +#: part/views.py:211 msgid "Part attachment updated" msgstr "" -#: part/views.py:224 +#: part/views.py:226 msgid "Delete Part Attachment" msgstr "" -#: part/views.py:232 +#: part/views.py:234 msgid "Deleted part attachment" msgstr "" -#: part/views.py:241 +#: part/views.py:243 msgid "Create Test Template" msgstr "" -#: part/views.py:270 +#: part/views.py:272 msgid "Edit Test Template" msgstr "" -#: part/views.py:286 +#: part/views.py:288 msgid "Delete Test Template" msgstr "" -#: part/views.py:295 +#: part/views.py:297 msgid "Set Part Category" msgstr "" -#: part/views.py:345 +#: part/views.py:347 #, python-brace-format msgid "Set category for {n} parts" msgstr "" -#: part/views.py:380 +#: part/views.py:382 msgid "Create Variant" msgstr "" -#: part/views.py:462 +#: part/views.py:464 msgid "Duplicate Part" msgstr "" -#: part/views.py:469 +#: part/views.py:471 msgid "Copied part" msgstr "" -#: part/views.py:523 part/views.py:653 +#: part/views.py:525 part/views.py:655 msgid "Possible matches exist - confirm creation of new part" msgstr "" -#: part/views.py:588 templates/js/stock.js:840 +#: part/views.py:590 templates/js/stock.js:840 msgid "Create New Part" msgstr "" -#: part/views.py:595 +#: part/views.py:597 msgid "Created new part" msgstr "" -#: part/views.py:811 +#: part/views.py:813 msgid "Part QR Code" msgstr "" -#: part/views.py:830 +#: part/views.py:832 msgid "Upload Part Image" msgstr "" -#: part/views.py:838 part/views.py:875 +#: part/views.py:840 part/views.py:877 msgid "Updated part image" msgstr "" -#: part/views.py:847 +#: part/views.py:849 msgid "Select Part Image" msgstr "" -#: part/views.py:878 +#: part/views.py:880 msgid "Part image not found" msgstr "" -#: part/views.py:889 +#: part/views.py:891 msgid "Edit Part Properties" msgstr "" -#: part/views.py:916 +#: part/views.py:918 msgid "Duplicate BOM" msgstr "" -#: part/views.py:947 +#: part/views.py:949 msgid "Confirm duplication of BOM from parent" msgstr "" -#: part/views.py:968 +#: part/views.py:970 msgid "Validate BOM" msgstr "" -#: part/views.py:991 +#: part/views.py:993 msgid "Confirm that the BOM is valid" msgstr "" -#: part/views.py:1002 +#: part/views.py:1004 msgid "Validated Bill of Materials" msgstr "" -#: part/views.py:1136 +#: part/views.py:1138 msgid "No BOM file provided" msgstr "" -#: part/views.py:1486 +#: part/views.py:1488 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1511 part/views.py:1514 +#: part/views.py:1513 part/views.py:1516 msgid "Select valid part" msgstr "" -#: part/views.py:1520 +#: part/views.py:1522 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1558 +#: part/views.py:1560 msgid "Select a part" msgstr "" -#: part/views.py:1564 +#: part/views.py:1566 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1568 +#: part/views.py:1570 msgid "Specify quantity" msgstr "" -#: part/views.py:1824 +#: part/views.py:1826 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1833 +#: part/views.py:1835 msgid "Part was deleted" msgstr "" -#: part/views.py:1842 +#: part/views.py:1844 msgid "Part Pricing" msgstr "" -#: part/views.py:1968 +#: part/views.py:1958 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1978 +#: part/views.py:1968 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1987 +#: part/views.py:1977 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1997 +#: part/views.py:1987 msgid "Create Part Parameter" msgstr "" -#: part/views.py:2049 +#: part/views.py:2039 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:2065 +#: part/views.py:2055 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:2124 +#: part/views.py:2114 msgid "Edit Part Category" msgstr "" -#: part/views.py:2161 +#: part/views.py:2151 msgid "Delete Part Category" msgstr "" -#: part/views.py:2169 +#: part/views.py:2159 msgid "Part category was deleted" msgstr "" -#: part/views.py:2232 +#: part/views.py:2222 msgid "Create BOM Item" msgstr "" -#: part/views.py:2300 +#: part/views.py:2290 msgid "Edit BOM item" msgstr "" -#: part/views.py:2350 +#: part/views.py:2340 msgid "Confim BOM item deletion" msgstr "" @@ -3491,291 +3478,295 @@ msgstr "" msgid "Enter unique serial numbers (or leave blank)" msgstr "" -#: stock/forms.py:191 +#: stock/forms.py:192 msgid "Label" msgstr "" -#: stock/forms.py:192 stock/forms.py:248 +#: stock/forms.py:193 stock/forms.py:249 msgid "Select test report template" msgstr "" -#: stock/forms.py:256 +#: stock/forms.py:257 msgid "Include stock items in sub locations" msgstr "" -#: stock/forms.py:291 +#: stock/forms.py:292 msgid "Stock item to install" msgstr "" -#: stock/forms.py:298 +#: stock/forms.py:299 msgid "Stock quantity to assign" msgstr "" -#: stock/forms.py:326 +#: stock/forms.py:327 msgid "Must not exceed available quantity" msgstr "" -#: stock/forms.py:336 +#: stock/forms.py:337 msgid "Destination location for uninstalled items" msgstr "" -#: stock/forms.py:338 +#: stock/forms.py:339 msgid "Add transaction note (optional)" msgstr "" -#: stock/forms.py:340 +#: stock/forms.py:341 msgid "Confirm uninstall" msgstr "" -#: stock/forms.py:340 +#: stock/forms.py:341 msgid "Confirm removal of installed stock items" msgstr "" -#: stock/forms.py:364 +#: stock/forms.py:365 msgid "Destination stock location" msgstr "" -#: stock/forms.py:366 +#: stock/forms.py:367 msgid "Add note (required)" msgstr "" -#: stock/forms.py:370 stock/views.py:916 stock/views.py:1114 +#: stock/forms.py:371 stock/views.py:916 stock/views.py:1114 msgid "Confirm stock adjustment" msgstr "" -#: stock/forms.py:370 +#: stock/forms.py:371 msgid "Confirm movement of stock items" msgstr "" -#: stock/forms.py:372 +#: stock/forms.py:373 msgid "Set Default Location" msgstr "" -#: stock/forms.py:372 +#: stock/forms.py:373 msgid "Set the destination as the default location for selected parts" msgstr "" -#: stock/models.py:178 +#: stock/models.py:179 msgid "Created stock item" msgstr "" -#: stock/models.py:214 +#: stock/models.py:215 msgid "StockItem with this serial number already exists" msgstr "" -#: stock/models.py:250 +#: stock/models.py:251 #, python-brace-format msgid "Part type ('{pf}') must be {pe}" msgstr "" -#: stock/models.py:260 stock/models.py:269 +#: stock/models.py:261 stock/models.py:270 msgid "Quantity must be 1 for item with a serial number" msgstr "" -#: stock/models.py:261 +#: stock/models.py:262 msgid "Serial number cannot be set if quantity greater than 1" msgstr "" -#: stock/models.py:283 +#: stock/models.py:284 msgid "Item cannot belong to itself" msgstr "" -#: stock/models.py:289 +#: stock/models.py:290 msgid "Item must have a build reference if is_building=True" msgstr "" -#: stock/models.py:296 +#: stock/models.py:297 msgid "Build reference does not point to the same part object" msgstr "" -#: stock/models.py:329 +#: stock/models.py:330 msgid "Parent Stock Item" msgstr "" -#: stock/models.py:338 +#: stock/models.py:339 msgid "Base part" msgstr "" -#: stock/models.py:347 +#: stock/models.py:348 msgid "Select a matching supplier part for this stock item" msgstr "" -#: stock/models.py:352 stock/templates/stock/stock_app_base.html:7 +#: stock/models.py:353 stock/templates/stock/stock_app_base.html:7 msgid "Stock Location" msgstr "" -#: stock/models.py:355 +#: stock/models.py:356 msgid "Where is this stock item located?" msgstr "" -#: stock/models.py:360 stock/templates/stock/item_base.html:212 +#: stock/models.py:361 stock/templates/stock/item_base.html:212 msgid "Installed In" msgstr "" -#: stock/models.py:363 +#: stock/models.py:364 msgid "Is this item installed in another item?" msgstr "" -#: stock/models.py:379 +#: stock/models.py:380 msgid "Serial number for this item" msgstr "" -#: stock/models.py:391 +#: stock/models.py:392 msgid "Batch code for this stock item" msgstr "" -#: stock/models.py:395 +#: stock/models.py:396 msgid "Stock Quantity" msgstr "" -#: stock/models.py:404 +#: stock/models.py:405 msgid "Source Build" msgstr "" -#: stock/models.py:406 +#: stock/models.py:407 msgid "Build for this stock item" msgstr "" -#: stock/models.py:417 +#: stock/models.py:418 msgid "Source Purchase Order" msgstr "" -#: stock/models.py:420 +#: stock/models.py:421 msgid "Purchase order for this stock item" msgstr "" -#: stock/models.py:426 +#: stock/models.py:427 msgid "Destination Sales Order" msgstr "" -#: stock/models.py:433 -msgid "Destination Build Order" -msgstr "" - -#: stock/models.py:446 +#: stock/models.py:439 msgid "Delete this Stock Item when stock is depleted" msgstr "" -#: stock/models.py:456 stock/templates/stock/item_notes.html:14 +#: stock/models.py:449 stock/templates/stock/item_notes.html:14 #: stock/templates/stock/item_notes.html:30 msgid "Stock Item Notes" msgstr "" -#: stock/models.py:507 -msgid "Assigned to Customer" +#: stock/models.py:457 stock/templates/stock/item_base.html:266 +msgid "Purchase Price" +msgstr "" + +#: stock/models.py:458 +msgid "Single unit purchase price at time of purchase" msgstr "" #: stock/models.py:509 +msgid "Assigned to Customer" +msgstr "" + +#: stock/models.py:511 msgid "Manually assigned to customer" msgstr "" -#: stock/models.py:522 +#: stock/models.py:524 msgid "Returned from customer" msgstr "" -#: stock/models.py:524 +#: stock/models.py:526 msgid "Returned to location" msgstr "" -#: stock/models.py:652 +#: stock/models.py:651 msgid "Installed into stock item" msgstr "" -#: stock/models.py:660 +#: stock/models.py:659 msgid "Installed stock item" msgstr "" -#: stock/models.py:684 +#: stock/models.py:683 msgid "Uninstalled stock item" msgstr "" -#: stock/models.py:703 +#: stock/models.py:702 msgid "Uninstalled into location" msgstr "" -#: stock/models.py:807 +#: stock/models.py:802 msgid "Part is not set as trackable" msgstr "" -#: stock/models.py:813 +#: stock/models.py:808 msgid "Quantity must be integer" msgstr "" -#: stock/models.py:819 +#: stock/models.py:814 #, python-brace-format msgid "Quantity must not exceed available stock quantity ({n})" msgstr "" -#: stock/models.py:822 +#: stock/models.py:817 msgid "Serial numbers must be a list of integers" msgstr "" -#: stock/models.py:825 +#: stock/models.py:820 msgid "Quantity does not match serial numbers" msgstr "" -#: stock/models.py:857 +#: stock/models.py:852 msgid "Add serial number" msgstr "" -#: stock/models.py:860 +#: stock/models.py:855 #, python-brace-format msgid "Serialized {n} items" msgstr "" -#: stock/models.py:971 +#: stock/models.py:966 msgid "StockItem cannot be moved as it is not in stock" msgstr "" -#: stock/models.py:1316 +#: stock/models.py:1320 msgid "Tracking entry title" msgstr "" -#: stock/models.py:1318 +#: stock/models.py:1322 msgid "Entry notes" msgstr "" -#: stock/models.py:1320 +#: stock/models.py:1324 msgid "Link to external page for further information" msgstr "" -#: stock/models.py:1380 +#: stock/models.py:1384 msgid "Value must be provided for this test" msgstr "" -#: stock/models.py:1386 +#: stock/models.py:1390 msgid "Attachment must be uploaded for this test" msgstr "" -#: stock/models.py:1403 +#: stock/models.py:1407 msgid "Test" msgstr "" -#: stock/models.py:1404 +#: stock/models.py:1408 msgid "Test name" msgstr "" -#: stock/models.py:1409 +#: stock/models.py:1413 msgid "Result" msgstr "" -#: stock/models.py:1410 templates/js/table_filters.js:162 +#: stock/models.py:1414 templates/js/table_filters.js:162 msgid "Test result" msgstr "" -#: stock/models.py:1416 +#: stock/models.py:1420 msgid "Test output value" msgstr "" -#: stock/models.py:1422 +#: stock/models.py:1426 msgid "Attachment" msgstr "" -#: stock/models.py:1423 +#: stock/models.py:1427 msgid "Test result attachment" msgstr "" -#: stock/models.py:1429 +#: stock/models.py:1433 msgid "Test notes" msgstr "" @@ -3905,32 +3896,32 @@ msgstr "" msgid "Stock Item Details" msgstr "" -#: stock/templates/stock/item_base.html:237 templates/js/build.js:426 +#: stock/templates/stock/item_base.html:231 templates/js/build.js:426 msgid "No location set" msgstr "" -#: stock/templates/stock/item_base.html:244 +#: stock/templates/stock/item_base.html:238 msgid "Barcode Identifier" msgstr "" -#: stock/templates/stock/item_base.html:258 templates/js/build.js:626 +#: stock/templates/stock/item_base.html:252 templates/js/build.js:626 #: templates/navbar.html:25 msgid "Build" msgstr "" -#: stock/templates/stock/item_base.html:272 +#: stock/templates/stock/item_base.html:273 msgid "Parent Item" msgstr "" -#: stock/templates/stock/item_base.html:297 +#: stock/templates/stock/item_base.html:298 msgid "Last Updated" msgstr "" -#: stock/templates/stock/item_base.html:302 +#: stock/templates/stock/item_base.html:303 msgid "Last Stocktake" msgstr "" -#: stock/templates/stock/item_base.html:306 +#: stock/templates/stock/item_base.html:307 msgid "No stocktake performed" msgstr "" @@ -4369,18 +4360,6 @@ msgstr "" msgid "Build Order Settings" msgstr "" -#: templates/InvenTree/settings/currency.html:5 -msgid "General Settings" -msgstr "" - -#: templates/InvenTree/settings/currency.html:14 -msgid "Currencies" -msgstr "" - -#: templates/InvenTree/settings/currency.html:18 -msgid "New Currency" -msgstr "" - #: templates/InvenTree/settings/global.html:10 msgid "Global InvenTree Settings" msgstr "" @@ -4393,19 +4372,19 @@ msgstr "" msgid "Part Options" msgstr "" -#: templates/InvenTree/settings/part.html:31 +#: templates/InvenTree/settings/part.html:33 msgid "Part Parameter Templates" msgstr "" -#: templates/InvenTree/settings/part.html:52 +#: templates/InvenTree/settings/part.html:54 msgid "No part parameter templates found" msgstr "" -#: templates/InvenTree/settings/part.html:72 +#: templates/InvenTree/settings/part.html:74 msgid "Edit Template" msgstr "" -#: templates/InvenTree/settings/part.html:73 +#: templates/InvenTree/settings/part.html:75 msgid "Delete Template" msgstr "" @@ -4455,10 +4434,6 @@ msgstr "" msgid "Global" msgstr "" -#: templates/InvenTree/settings/tabs.html:19 -msgid "Currency" -msgstr "" - #: templates/InvenTree/settings/theme.html:10 msgid "Theme Settings" msgstr "" @@ -5188,38 +5163,38 @@ msgstr "" msgid "Important dates" msgstr "" -#: users/models.py:130 +#: users/models.py:131 msgid "Permission set" msgstr "" -#: users/models.py:138 +#: users/models.py:139 msgid "Group" msgstr "" -#: users/models.py:141 +#: users/models.py:142 msgid "View" msgstr "" -#: users/models.py:141 +#: users/models.py:142 msgid "Permission to view items" msgstr "" -#: users/models.py:143 +#: users/models.py:144 msgid "Add" msgstr "" -#: users/models.py:143 +#: users/models.py:144 msgid "Permission to add items" msgstr "" -#: users/models.py:145 +#: users/models.py:146 msgid "Change" msgstr "" -#: users/models.py:145 +#: users/models.py:146 msgid "Permissions to edit items" msgstr "" -#: users/models.py:147 +#: users/models.py:148 msgid "Permission to delete items" msgstr "" diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index a5b9021807..bede8b59db 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -108,6 +108,8 @@ class RuleSet(models.Model): 'report_reportasset', 'report_testreport', 'part_partstar', + 'exchange_rate', + 'exchange_exchangebackend', ] RULE_OPTIONS = [ From fc89501a629c36477ce99b6a9f0dedd8f6511cfb Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 11 Nov 2020 08:06:14 +1100 Subject: [PATCH 28/52] Fix for SQL cursor query - What works in SQLite don't necessarily fly with the big boys --- .../migrations/0026_auto_20201110_1011.py | 18 ++++++++++++------ .../part/migrations/0056_auto_20201110_1125.py | 18 ++++++++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/InvenTree/company/migrations/0026_auto_20201110_1011.py b/InvenTree/company/migrations/0026_auto_20201110_1011.py index ffaf99842c..24fac6f52d 100644 --- a/InvenTree/company/migrations/0026_auto_20201110_1011.py +++ b/InvenTree/company/migrations/0026_auto_20201110_1011.py @@ -27,11 +27,13 @@ def migrate_currencies(apps, schema_editor): cursor = connection.cursor() # The 'suffix' field denotes the currency code - response = cursor.execute('SELECT id, suffix, description from common_currency;').fetchall() + response = cursor.execute('SELECT id, suffix, description from common_currency;') + + results = cursor.fetchall() remap = {} - for index, row in enumerate(response): + for index, row in enumerate(results): pk, suffix, description = row suffix = suffix.strip().upper() @@ -49,11 +51,13 @@ def migrate_currencies(apps, schema_editor): remap[pk] = suffix # Now iterate through each SupplierPriceBreak and update the rows - response = cursor.execute('SELECT id, cost, currency_id, price, price_currency from part_supplierpricebreak;').fetchall() + response = cursor.execute('SELECT id, cost, currency_id, price, price_currency from part_supplierpricebreak;') + + results = cursor.fetchall() count = 0 - for index, row in enumerate(response): + for index, row in enumerate(results): pk, cost, currency_id, price, price_currency = row # Copy the 'cost' field across to the 'price' field @@ -82,11 +86,13 @@ def reverse_currencies(apps, schema_editor): cursor = connection.cursor() # Extract a list of currency codes which are in use - response = cursor.execute(f'SELECT id, price, price_currency from part_supplierpricebreak;').fetchall() + response = cursor.execute(f'SELECT id, price, price_currency from part_supplierpricebreak;') + + results = cursor.fetchall() codes_in_use = set() - for index, row in enumerate(response): + for index, row in enumerate(results): pk, price, code = row codes_in_use.add(code) diff --git a/InvenTree/part/migrations/0056_auto_20201110_1125.py b/InvenTree/part/migrations/0056_auto_20201110_1125.py index e15409daec..dff86173b6 100644 --- a/InvenTree/part/migrations/0056_auto_20201110_1125.py +++ b/InvenTree/part/migrations/0056_auto_20201110_1125.py @@ -27,11 +27,13 @@ def migrate_currencies(apps, schema_editor): cursor = connection.cursor() # The 'suffix' field denotes the currency code - response = cursor.execute('SELECT id, suffix, description from common_currency;').fetchall() + response = cursor.execute('SELECT id, suffix, description from common_currency;') + + results = cursor.fetchall() remap = {} - for index, row in enumerate(response): + for index, row in enumerate(results): pk, suffix, description = row suffix = suffix.strip().upper() @@ -49,11 +51,13 @@ def migrate_currencies(apps, schema_editor): remap[pk] = suffix # Now iterate through each PartSellPriceBreak and update the rows - response = cursor.execute('SELECT id, cost, currency_id, price, price_currency from part_partsellpricebreak;').fetchall() + response = cursor.execute('SELECT id, cost, currency_id, price, price_currency from part_partsellpricebreak;') + + results = cursor.fetchall() count = 0 - for index, row in enumerate(response): + for index, row in enumerate(results): pk, cost, currency_id, price, price_currency = row # Copy the 'cost' field across to the 'price' field @@ -82,11 +86,13 @@ def reverse_currencies(apps, schema_editor): cursor = connection.cursor() # Extract a list of currency codes which are in use - response = cursor.execute(f'SELECT id, price, price_currency from part_partsellpricebreak;').fetchall() + response = cursor.execute(f'SELECT id, price, price_currency from part_partsellpricebreak;') + + results = cursor.fetchall() codes_in_use = set() - for index, row in enumerate(response): + for index, row in enumerate(results): pk, price, code = row codes_in_use.add(code) From ebac06ebee9b9dc7c224c3c1deccdae42cdc7e94 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 11 Nov 2020 13:55:25 +1100 Subject: [PATCH 29/52] Add 'single_pricing' form to the EditSupplierPartForm - Idea here is to automatically create a unit-pricing price-break when a new SupplierPart is created --- InvenTree/company/forms.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/InvenTree/company/forms.py b/InvenTree/company/forms.py index 9ebb8839f3..7a27f08251 100644 --- a/InvenTree/company/forms.py +++ b/InvenTree/company/forms.py @@ -8,6 +8,12 @@ from __future__ import unicode_literals from InvenTree.forms import HelperForm from InvenTree.fields import RoundingDecimalFormField +from django.utils.translation import ugettext as _ + +from djmoney.forms.fields import MoneyField + +from common.models import InvenTreeSetting + from .models import Company from .models import SupplierPart from .models import SupplierPriceBreak @@ -60,6 +66,15 @@ class EditSupplierPartForm(HelperForm): 'note': 'fa-pencil-alt', } + single_pricing = MoneyField( + label=_('Single Price'), + default_currency=InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY'), + help_text=_('Single quantity price'), + decimal_places=4, + max_digits=19, + required=False, + ) + class Meta: model = SupplierPart fields = [ @@ -71,8 +86,9 @@ class EditSupplierPartForm(HelperForm): 'MPN', 'link', 'note', - 'base_cost', - 'multiple', + 'single_pricing', + # 'base_cost', + # 'multiple', 'packaging', ] From bfdda847c449fa09a87ed3ef37946fe6bbe957c9 Mon Sep 17 00:00:00 2001 From: eeintech Date: Wed, 11 Nov 2020 11:18:10 -0500 Subject: [PATCH 30/52] Updated part migration reference in 0054 --- InvenTree/part/migrations/0054_auto_20201109_1246.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/part/migrations/0054_auto_20201109_1246.py b/InvenTree/part/migrations/0054_auto_20201109_1246.py index 705ef51466..830525763e 100644 --- a/InvenTree/part/migrations/0054_auto_20201109_1246.py +++ b/InvenTree/part/migrations/0054_auto_20201109_1246.py @@ -7,7 +7,7 @@ import part.settings class Migration(migrations.Migration): dependencies = [ - ('part', '0053_merge_20201103_1028'), + ('part', '0053_partcategoryparametertemplate'), ] operations = [ From b4fa56fd9644116611af8ef54135383c3031804a Mon Sep 17 00:00:00 2001 From: eeintech Date: Wed, 11 Nov 2020 12:40:03 -0500 Subject: [PATCH 31/52] Fixed PART_CATEGORY_PARAMETERS duplicate (bad merging... oopsy) --- InvenTree/common/models.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 46e4a5a94a..2173df0ae7 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -92,13 +92,6 @@ class InvenTreeSetting(models.Model): 'validator': bool }, - 'PART_CATEGORY_PARAMETERS': { - 'name': _('Copy Category Parameter Templates'), - 'description': _('Copy category parameter templates when creating a part'), - 'default': True, - 'validator': bool - }, - 'PART_CATEGORY_PARAMETERS': { 'name': _('Copy Category Parameter Templates'), 'description': _('Copy category parameter templates when creating a part'), From 1532be9c1e097dad3f4a3c6f10dc21583b8c2e6f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 11:02:10 +1100 Subject: [PATCH 32/52] Add 'currency' option for company - e.g. an external supplier might have a default currency - Adds a form input which only allows selection of allowed currency codes - Add unit testing for currency validation --- InvenTree/InvenTree/validators.py | 11 +++ InvenTree/common/settings.py | 23 ++++++ InvenTree/company/forms.py | 11 +++ .../migrations/0029_company_currency.py | 19 +++++ InvenTree/company/models.py | 11 +++ .../company/templates/company/detail.html | 77 ++++++++++++++----- InvenTree/company/tests.py | 25 ++++++ 7 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 InvenTree/common/settings.py create mode 100644 InvenTree/company/migrations/0029_company_currency.py diff --git a/InvenTree/InvenTree/validators.py b/InvenTree/InvenTree/validators.py index e85dc40810..70322df062 100644 --- a/InvenTree/InvenTree/validators.py +++ b/InvenTree/InvenTree/validators.py @@ -6,11 +6,22 @@ from django.conf import settings from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ +from moneyed import CURRENCIES + import common.models import re +def validate_currency_code(code): + """ + Check that a given code is a valid currency code. + """ + + if code not in CURRENCIES: + raise ValidationError(_('Not a valid currency code')) + + def allowable_url_schemes(): """ Return the list of allowable URL schemes. In addition to the default schemes allowed by Django, diff --git a/InvenTree/common/settings.py b/InvenTree/common/settings.py new file mode 100644 index 0000000000..832a07f040 --- /dev/null +++ b/InvenTree/common/settings.py @@ -0,0 +1,23 @@ +""" +User-configurable settings for the common app +""" + +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from moneyed import CURRENCIES + +from common.models import InvenTreeSetting + + +def currency_code_default(): + """ + Returns the default currency code (or USD if not specified) + """ + + code = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') + + if code not in CURRENCIES: + code = 'USD' + + return code diff --git a/InvenTree/company/forms.py b/InvenTree/company/forms.py index 7a27f08251..27f2dce0dc 100644 --- a/InvenTree/company/forms.py +++ b/InvenTree/company/forms.py @@ -9,10 +9,13 @@ from InvenTree.forms import HelperForm from InvenTree.fields import RoundingDecimalFormField from django.utils.translation import ugettext as _ +import django.forms +import djmoney.settings from djmoney.forms.fields import MoneyField from common.models import InvenTreeSetting +import common.settings from .models import Company from .models import SupplierPart @@ -30,6 +33,13 @@ class EditCompanyForm(HelperForm): 'phone': 'fa-phone', } + currency = django.forms.ChoiceField( + required=False, + help_text=_('Default currency used for this company'), + choices=[('', '----------')] + djmoney.settings.CURRENCY_CHOICES, + initial=common.settings.currency_code_default, + ) + class Meta: model = Company fields = [ @@ -37,6 +47,7 @@ class EditCompanyForm(HelperForm): 'description', 'website', 'address', + 'currency', 'phone', 'email', 'contact', diff --git a/InvenTree/company/migrations/0029_company_currency.py b/InvenTree/company/migrations/0029_company_currency.py new file mode 100644 index 0000000000..fda3c15e96 --- /dev/null +++ b/InvenTree/company/migrations/0029_company_currency.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.7 on 2020-11-11 23:22 + +import InvenTree.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0028_remove_supplierpricebreak_cost'), + ] + + operations = [ + migrations.AddField( + model_name='company', + name='currency', + field=models.CharField(blank=True, help_text='Default currency used for this company', max_length=3, validators=[InvenTree.validators.validate_currency_code], verbose_name='Currency'), + ), + ] diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 75b765bb2e..711a1966ff 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -26,6 +26,8 @@ from InvenTree.helpers import normalize from InvenTree.fields import InvenTreeURLField from InvenTree.status_codes import PurchaseOrderStatus +import InvenTree.validators + import common.models @@ -77,6 +79,7 @@ class Company(models.Model): is_customer: boolean value, is this company a customer is_supplier: boolean value, is this company a supplier is_manufacturer: boolean value, is this company a manufacturer + currency_code: Specifies the default currency for the company """ class Meta: @@ -126,6 +129,14 @@ class Company(models.Model): is_manufacturer = models.BooleanField(default=False, help_text=_('Does this company manufacture parts?')) + currency = models.CharField( + max_length=3, + verbose_name=_('Currency'), + blank=True, + help_text=_('Default currency used for this company'), + validators=[InvenTree.validators.validate_currency_code], + ) + def __str__(self): """ Get string representation of a Company """ return "{n} - {d}".format(n=self.name, d=self.description) diff --git a/InvenTree/company/templates/company/detail.html b/InvenTree/company/templates/company/detail.html index a166de9048..7243cf4fbc 100644 --- a/InvenTree/company/templates/company/detail.html +++ b/InvenTree/company/templates/company/detail.html @@ -8,25 +8,64 @@

    {% trans "Company Details" %}


    - - - - - - - - - - - - - - - - - - -
    {% trans "Manufacturer" %}{% include "yesnolabel.html" with value=company.is_manufacturer %}
    {% trans "Supplier" %}{% include 'yesnolabel.html' with value=company.is_supplier %}
    {% trans "Customer" %}{% include 'yesnolabel.html' with value=company.is_customer %}
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    {% trans "Company Name" %}{{ company.name }}
    {% trans "Description" %}{{ company.description }}
    {% trans "Website" %} + {% if company.website %}{{ company.website }} + {% else %}{% trans "No website specified" %} + {% endif %} +
    {% trans "Currency" %} + {% if company.currency %}{{ company.currency }} + {% else %}{% trans "Uses default currency" %} + {% endif %} +
    +
    +
    + + + + + + + + + + + + + + + + + + +
    {% trans "Manufacturer" %}{% include "yesnolabel.html" with value=company.is_manufacturer %}
    {% trans "Supplier" %}{% include 'yesnolabel.html' with value=company.is_supplier %}
    {% trans "Customer" %}{% include 'yesnolabel.html' with value=company.is_customer %}
    + +
    +
    {% endblock %} {% block js_ready %} diff --git a/InvenTree/company/tests.py b/InvenTree/company/tests.py index 24c7e3a20f..5229fdb045 100644 --- a/InvenTree/company/tests.py +++ b/InvenTree/company/tests.py @@ -1,4 +1,5 @@ from django.test import TestCase +from django.core.exceptions import ValidationError import os @@ -119,6 +120,30 @@ class CompanySimpleTest(TestCase): self.assertIsNone(m3x12.get_price_info(3)) self.assertIsNotNone(m3x12.get_price_info(50)) + def test_currency_validation(self): + """ + Test validation for currency selection + """ + + # Create a company with a valid currency code (should pass) + company = Company.objects.create( + name='Test', + description='Toast', + currency='AUD', + ) + + company.full_clean() + + # Create a company with an invalid currency code (should fail) + company = Company.objects.create( + name='test', + description='Toasty', + currency='XZY', + ) + + with self.assertRaises(ValidationError): + company.full_clean() + class ContactSimpleTest(TestCase): From 51d2d85c267544239be91360dd6f5dca5a523ecd Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 11:14:50 +1100 Subject: [PATCH 33/52] When creating a new price break for a supplier part, default to using the currency code specified for the supplier company --- InvenTree/common/models.py | 9 ++++- InvenTree/company/forms.py | 7 +++- .../migrations/0030_auto_20201112_1112.py | 20 +++++++++++ InvenTree/company/models.py | 19 +++++++++++ InvenTree/company/views.py | 33 +++++++++++++++---- .../migrations/0059_auto_20201112_1112.py | 20 +++++++++++ 6 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 InvenTree/company/migrations/0030_auto_20201112_1112.py create mode 100644 InvenTree/part/migrations/0059_auto_20201112_1112.py diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 4ebd60e8b0..e4e1bc1718 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -464,7 +464,14 @@ class PriceBreak(models.Model): class Meta: abstract = True - quantity = InvenTree.fields.RoundingDecimalField(max_digits=15, decimal_places=5, default=1, validators=[MinValueValidator(1)]) + quantity = InvenTree.fields.RoundingDecimalField( + max_digits=15, + decimal_places=5, + default=1, + validators=[MinValueValidator(1)], + verbose_name=_('Quantity'), + help_text=_('Price break quantity'), + ) price = MoneyField( max_digits=19, diff --git a/InvenTree/company/forms.py b/InvenTree/company/forms.py index 27f2dce0dc..da90286b35 100644 --- a/InvenTree/company/forms.py +++ b/InvenTree/company/forms.py @@ -107,7 +107,12 @@ class EditSupplierPartForm(HelperForm): class EditPriceBreakForm(HelperForm): """ Form for creating / editing a supplier price break """ - quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5) + quantity = RoundingDecimalFormField( + max_digits=10, + decimal_places=5, + label=_('Quantity'), + help_text=_('Price break quantity'), + ) class Meta: model = SupplierPriceBreak diff --git a/InvenTree/company/migrations/0030_auto_20201112_1112.py b/InvenTree/company/migrations/0030_auto_20201112_1112.py new file mode 100644 index 0000000000..367ed303e5 --- /dev/null +++ b/InvenTree/company/migrations/0030_auto_20201112_1112.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.7 on 2020-11-12 00:12 + +import InvenTree.fields +import django.core.validators +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0029_company_currency'), + ] + + operations = [ + migrations.AlterField( + model_name='supplierpricebreak', + name='quantity', + field=InvenTree.fields.RoundingDecimalField(decimal_places=5, default=1, help_text='Price break quantity', max_digits=15, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Quantity'), + ), + ] diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 711a1966ff..241c5fe1f7 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -17,6 +17,8 @@ from django.db.models import Sum, Q, UniqueConstraint from django.apps import apps from django.urls import reverse +from moneyed import CURRENCIES + from markdownx.models import MarkdownxField from stdimage.models import StdImageField @@ -29,6 +31,7 @@ from InvenTree.status_codes import PurchaseOrderStatus import InvenTree.validators import common.models +import common.settings def rename_company_image(instance, filename): @@ -137,6 +140,22 @@ class Company(models.Model): validators=[InvenTree.validators.validate_currency_code], ) + @property + def currency_code(self): + """ + Return the currency code associated with this company. + + - If the currency code is invalid, use the default currency + - If the currency code is not specified, use the default currency + """ + + code = self.currency + + if code not in CURRENCIES: + code = common.settings.currency_code_default() + + return code + def __str__(self): """ Get string representation of a Company """ return "{n} - {d}".format(n=self.name, d=self.description) diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py index abdad55322..758e8a2990 100644 --- a/InvenTree/company/views.py +++ b/InvenTree/company/views.py @@ -30,6 +30,7 @@ from .forms import EditSupplierPartForm from .forms import EditPriceBreakForm import common.models +import common.settings class CompanyIndex(InvenTreeRoleMixin, ListView): @@ -419,10 +420,23 @@ class PriceBreakCreate(AjaxCreateView): } def get_part(self): + """ + Attempt to extract SupplierPart object from the supplied data. + """ + try: - return SupplierPart.objects.get(id=self.request.GET.get('part')) - except SupplierPart.DoesNotExist: - return SupplierPart.objects.get(id=self.request.POST.get('part')) + supplier_part = SupplierPart.objects.get(pk=self.request.GET.get('part')) + return supplier_part + except (ValueError, SupplierPart.DoesNotExist): + pass + + try: + supplier_part = SupplierPart.objects.get(pk=self.request.POST.get('part')) + return supplier_part + except (ValueError, SupplierPart.DoesNotExist): + pass + + return None def get_form(self): @@ -435,12 +449,19 @@ class PriceBreakCreate(AjaxCreateView): initials = super(AjaxCreateView, self).get_initial() + supplier_part = self.get_part() + initials['part'] = self.get_part() - default_currency = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') - currency = CURRENCIES.get(default_currency, None) + if supplier_part is not None: + currency_code = supplier_part.supplier.currency_code + else: + currency_code = common.settings.currency_code_default() - if currency is not None: + # Extract the currency object associated with the code + currency = CURRENCIES.get(currency_code, None) + + if currency: initials['price'] = [1.0, currency] return initials diff --git a/InvenTree/part/migrations/0059_auto_20201112_1112.py b/InvenTree/part/migrations/0059_auto_20201112_1112.py new file mode 100644 index 0000000000..5075b8431c --- /dev/null +++ b/InvenTree/part/migrations/0059_auto_20201112_1112.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.7 on 2020-11-12 00:12 + +import InvenTree.fields +import django.core.validators +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0058_remove_partsellpricebreak_cost'), + ] + + operations = [ + migrations.AlterField( + model_name='partsellpricebreak', + name='quantity', + field=InvenTree.fields.RoundingDecimalField(decimal_places=5, default=1, help_text='Price break quantity', max_digits=15, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Quantity'), + ), + ] From 391eeb0e46c02520438b50a2dc66a94650d8d4ba Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 11:50:59 +1100 Subject: [PATCH 34/52] Specify default currency when creating a new stock item --- InvenTree/stock/views.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 1d58647d5a..88c2274ddc 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -14,6 +14,8 @@ from django.urls import reverse from django.utils.translation import ugettext as _ +from moneyed import CURRENCIES + from InvenTree.views import AjaxView from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView from InvenTree.views import QRCodeView @@ -32,6 +34,8 @@ from report.models import TestReport from label.models import StockItemLabel from .models import StockItem, StockLocation, StockItemTracking, StockItemAttachment, StockItemTestResult +import common.settings + from .admin import StockItemResource from . import forms as StockForms @@ -1572,6 +1576,8 @@ class StockItemCreate(AjaxCreateView): initials['location'] = part.get_default_location() initials['supplier_part'] = part.default_supplier + currency_code = common.settings.currency_code_default() + # SupplierPart field has been specified # It must match the Part, if that has been supplied if sup_part_id: @@ -1581,6 +1587,8 @@ class StockItemCreate(AjaxCreateView): if part is None or supplier_part.part == part: initials['supplier_part'] = supplier_part + currency_code = supplier_part.supplier.currency_code + except (ValueError, SupplierPart.DoesNotExist): pass @@ -1592,6 +1600,11 @@ class StockItemCreate(AjaxCreateView): except (ValueError, StockLocation.DoesNotExist): pass + currency = CURRENCIES.get(currency_code, None) + + if currency: + initials['purchase_price'] = (None, currency) + return initials def post(self, request, *args, **kwargs): From b7187c5e0611455fa4308d41d5b04c14191372af Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 12:27:01 +1100 Subject: [PATCH 35/52] Fixes for purchase order table displays --- InvenTree/order/templates/order/purchase_order_detail.html | 3 +++ InvenTree/templates/js/order.js | 1 + 2 files changed, 4 insertions(+) diff --git a/InvenTree/order/templates/order/purchase_order_detail.html b/InvenTree/order/templates/order/purchase_order_detail.html index 2f9e57ca99..b9eb27d27c 100644 --- a/InvenTree/order/templates/order/purchase_order_detail.html +++ b/InvenTree/order/templates/order/purchase_order_detail.html @@ -146,6 +146,7 @@ $("#po-table").inventreeTable({ field: 'part', sortable: true, title: '{% trans "Part" %}', + switchable: false, formatter: function(value, row, index, field) { if (row.part) { return imageHoverIcon(row.part_detail.thumbnail) + renderLink(row.part_detail.full_name, `/part/${row.part_detail.pk}/`); @@ -180,6 +181,7 @@ $("#po-table").inventreeTable({ { sortable: true, field: 'received', + switchable: false, title: '{% trans "Received" %}', formatter: function(value, row, index, field) { return makeProgressBar(row.received, row.quantity, { @@ -203,6 +205,7 @@ $("#po-table").inventreeTable({ title: '{% trans "Notes" %}', }, { + switchable: false, field: 'buttons', title: '', formatter: function(value, row, index, field) { diff --git a/InvenTree/templates/js/order.js b/InvenTree/templates/js/order.js index 0c958c65a2..41a1b4c046 100644 --- a/InvenTree/templates/js/order.js +++ b/InvenTree/templates/js/order.js @@ -137,6 +137,7 @@ function loadPurchaseOrderTable(table, options) { sortable: true, field: 'reference', title: '{% trans "Purchase Order" %}', + switchable: false, formatter: function(value, row, index, field) { var prefix = "{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}"; From 4a8170079e2b715d40e94f5d407d110a635f8a5d Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 12:31:03 +1100 Subject: [PATCH 36/52] Remove code which automatically created settings objects on server launch --- InvenTree/common/apps.py | 85 +--------------------------------------- 1 file changed, 1 insertion(+), 84 deletions(-) diff --git a/InvenTree/common/apps.py b/InvenTree/common/apps.py index 06b825c574..0acc72c377 100644 --- a/InvenTree/common/apps.py +++ b/InvenTree/common/apps.py @@ -6,87 +6,4 @@ class CommonConfig(AppConfig): name = 'common' def ready(self): - - """ Will be called when the Common app is first loaded """ - self.add_instance_name() - self.add_default_settings() - - def add_instance_name(self): - """ - Check if an InstanceName has been defined for this database. - If not, create a random one! - """ - - # See note above - from .models import InvenTreeSetting - - """ - Note: The "old" instance name was stored under the key 'InstanceName', - but has now been renamed to 'INVENTREE_INSTANCE'. - """ - - try: - - # Quick exit if a value already exists for 'inventree_instance' - if InvenTreeSetting.objects.filter(key='INVENTREE_INSTANCE').exists(): - return - - # Default instance name - instance_name = InvenTreeSetting.get_default_value('INVENTREE_INSTANCE') - - # Use the old name if it exists - if InvenTreeSetting.objects.filter(key='InstanceName').exists(): - instance = InvenTreeSetting.objects.get(key='InstanceName') - instance_name = instance.value - - # Delete the legacy key - instance.delete() - - # Create new value - InvenTreeSetting.objects.create( - key='INVENTREE_INSTANCE', - value=instance_name - ) - - except (OperationalError, ProgrammingError, IntegrityError): - # Migrations have not yet been applied - table does not exist - pass - - def add_default_settings(self): - """ - Create all required settings, if they do not exist. - """ - - from .models import InvenTreeSetting - - for key in InvenTreeSetting.GLOBAL_SETTINGS.keys(): - try: - settings = InvenTreeSetting.objects.filter(key__iexact=key) - - if settings.count() == 0: - value = InvenTreeSetting.get_default_value(key) - - print(f"Creating default setting for {key} -> '{value}'") - - InvenTreeSetting.objects.create( - key=key, - value=value - ) - - return - - elif settings.count() > 1: - # Prevent multiple shadow copies of the same setting! - for setting in settings[1:]: - setting.delete() - - # Ensure that the key has the correct case - setting = settings[0] - - if not setting.key == key: - setting.key = key - setting.save() - - except (OperationalError, ProgrammingError, IntegrityError): - # Table might not yet exist - pass + pass From ec8d8e5a644c5f44e78a9ace2e77273cf88b81d3 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 13:31:27 +1100 Subject: [PATCH 37/52] Add more invoke commands: - export-records: Exports all database records to external file - import-records: Imports database records from external file - import-fixtures: Fills the database with dummy records --- .travis.yml | 12 ++++ InvenTree/common/apps.py | 3 +- InvenTree/common/fixtures/currency.yaml | 16 ----- tasks.py | 87 +++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 17 deletions(-) delete mode 100644 InvenTree/common/fixtures/currency.yaml diff --git a/.travis.yml b/.travis.yml index 67d8c0502a..4f772f6d5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,11 +30,23 @@ before_install: script: - cd InvenTree && python3 manage.py makemigrations && cd .. - python3 ci/check_migration_files.py + # Run unit testing / code coverage tests - invoke coverage + # Run unit test for SQL database backend - cd InvenTree && python3 manage.py test --settings=InvenTree.ci_mysql && cd .. + # Run unit test for PostgreSQL database backend - cd InvenTree && python3 manage.py test --settings=InvenTree.ci_postgresql && cd .. - invoke translate - invoke style + # Create an empty database and fill it with test data + - rm inventree_db.sqlite3 + - invoke migrate + - invoke import-fixtures + # Export database records + - invoke export-records -f data.json + # Create a new empty database and import the saved data + - rm inventree_db.sqlite3 + - invoke import-records -f data.json after_success: - coveralls \ No newline at end of file diff --git a/InvenTree/common/apps.py b/InvenTree/common/apps.py index 0acc72c377..34b43fc68b 100644 --- a/InvenTree/common/apps.py +++ b/InvenTree/common/apps.py @@ -1,5 +1,6 @@ +# -*- coding: utf-8 -*- + from django.apps import AppConfig -from django.db.utils import OperationalError, ProgrammingError, IntegrityError class CommonConfig(AppConfig): diff --git a/InvenTree/common/fixtures/currency.yaml b/InvenTree/common/fixtures/currency.yaml deleted file mode 100644 index 639b0751df..0000000000 --- a/InvenTree/common/fixtures/currency.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Test fixtures for Currency objects - -- model: common.currency - fields: - symbol: '$' - suffix: 'AUD' - description: 'Australian Dollars' - base: True - -- model: common.currency - fields: - symbol: '$' - suffix: 'USD' - description: 'US Dollars' - base: False - value: 1.4 \ No newline at end of file diff --git a/tasks.py b/tasks.py index 49f3f9445b..0c33fcb4c7 100644 --- a/tasks.py +++ b/tasks.py @@ -238,6 +238,93 @@ def postgresql(c): c.run('sudo apt-get install postgresql postgresql-contrib libpq-dev') c.run('pip3 install psycopg2') +@task(help={'filename': "Output filename (default = 'data.json')"}) +def export_records(c, filename='data.json'): + """ + Export all database records to a file + """ + + # Get an absolute path to the file + if not os.path.isabs(filename): + filename = os.path.join(localDir(), filename) + filename = os.path.abspath(filename) + + print(f"Exporting database records to file '{filename}'") + + if os.path.exists(filename): + response = input("Warning: file already exists. Do you want to overwrite? [y/N]: ") + response = str(response).strip().lower() + + if response not in ['y', 'yes']: + print("Cancelled export operation") + return 0 + + cmd = f'dumpdata --exclude contenttypes --exclude auth.permission --indent 2 --output {filename}' + + manage(c, cmd, pty=True) + +@task(help={'filename': 'Input filename'}) +def import_records(c, filename='data.json'): + """ + Import database records from a file + """ + + # Get an absolute path to the supplied filename + if not os.path.isabs(filename): + filename = os.path.join(localDir(), filename) + + if not os.path.exists(filename): + print(f"Error: File '{filename}' does not exist") + return -1 + + print(f"Importing database records from '{filename}'") + + cmd = f'loaddata {filename}' + + manage(c, cmd, pty=True) + +@task +def import_fixtures(c): + """ + Import fixture data into the database. + + This command imports all existing test fixture data into the database. + + Warning: + - Intended for testing / development only! + - Running this command may overwrite existing database data!! + - Don't say you were not warned... + """ + + fixtures = [ + # Build model + 'build', + + # Company model + 'company', + 'price_breaks', + 'supplier_part', + + # Order model + 'order', + + # Part model + 'bom', + 'category', + 'params', + 'part', + 'test_templates', + + # Stock model + 'location', + 'stock_tests', + 'stock', + ] + + command = 'loaddata ' + ' '.join(fixtures) + + manage(c, command, pty=True) + @task def backup(c): """ From 1738df90427b99762149a574f6779f7293e152c2 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 14:48:57 +1100 Subject: [PATCH 38/52] Update unit tests --- InvenTree/common/tests.py | 33 +++++++++++++++++++-------------- tasks.py | 3 +++ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 323049f164..365b9d0980 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -4,20 +4,7 @@ from __future__ import unicode_literals from django.test import TestCase from django.contrib.auth import get_user_model -from .models import Currency, InvenTreeSetting - - -class CurrencyTest(TestCase): - """ Tests for Currency model """ - - fixtures = [ - 'currency', - ] - - def test_currency(self): - # Simple test for now (improve this later!) - - self.assertEqual(Currency.objects.count(), 2) +from .models import InvenTreeSetting class SettingsTest(TestCase): @@ -25,6 +12,10 @@ class SettingsTest(TestCase): Tests for the 'settings' model """ + fixtures = [ + 'settings', + ] + def setUp(self): User = get_user_model() @@ -35,6 +26,20 @@ class SettingsTest(TestCase): self.client.login(username='username', password='password') + def test_settings_objects(self): + + # There should be two settings objects in the database + settings = InvenTreeSetting.objects.all() + + self.assertEqual(settings.count(), 2) + + instance_name = InvenTreeSetting.objects.get(pk=1) + self.assertEqual(instance_name.key, 'INVENTREE_INSTANCE') + self.assertEqual(instance_name.value, 'My very first InvenTree Instance') + + self.assertEqual(InvenTreeSetting.get_setting_object('iNvEnTrEE_inSTanCE').pk, 21) + + def test_required_values(self): """ - Ensure that every global setting has a name. diff --git a/tasks.py b/tasks.py index 0c33fcb4c7..f4ddf5e0b6 100644 --- a/tasks.py +++ b/tasks.py @@ -300,6 +300,9 @@ def import_fixtures(c): # Build model 'build', + # Common models + 'settings', + # Company model 'company', 'price_breaks', From 21315096d464583e81fc0eb5ce957b8e6789dff1 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 14:53:49 +1100 Subject: [PATCH 39/52] Further unit testing fixes --- InvenTree/common/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 365b9d0980..649ded617f 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -37,7 +37,8 @@ class SettingsTest(TestCase): self.assertEqual(instance_name.key, 'INVENTREE_INSTANCE') self.assertEqual(instance_name.value, 'My very first InvenTree Instance') - self.assertEqual(InvenTreeSetting.get_setting_object('iNvEnTrEE_inSTanCE').pk, 21) + # Check object lookup (case insensitive) + self.assertEqual(InvenTreeSetting.get_setting_object('iNvEnTrEE_inSTanCE').pk, 1) def test_required_values(self): From fe9749ba4f9bfd375cc9ace3a692448a2aace569 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 14:54:03 +1100 Subject: [PATCH 40/52] Add missing fixture for settings --- InvenTree/common/fixtures/settings.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 InvenTree/common/fixtures/settings.yaml diff --git a/InvenTree/common/fixtures/settings.yaml b/InvenTree/common/fixtures/settings.yaml new file mode 100644 index 0000000000..70ce23f312 --- /dev/null +++ b/InvenTree/common/fixtures/settings.yaml @@ -0,0 +1,13 @@ +# Sample settings objects + +- model: common.InvenTreeSetting + pk: 1 + fields: + key: INVENTREE_INSTANCE + value: "My very first InvenTree Instance" + +- model: common.InvenTreeSetting + pk: 2 + fields: + key: INVENTREE_COMPANY_NAME + value: "ACME Pty Ltd" \ No newline at end of file From 96ef5e1bde25df1e9991b8d92ad6f28ba34a3562 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 15:37:21 +1100 Subject: [PATCH 41/52] Travis fixes --- .travis.yml | 4 ++-- InvenTree/common/tests.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4f772f6d5d..150afdd763 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,13 +39,13 @@ script: - invoke translate - invoke style # Create an empty database and fill it with test data - - rm inventree_db.sqlite3 + - rm inventree_default_db.sqlite3 - invoke migrate - invoke import-fixtures # Export database records - invoke export-records -f data.json # Create a new empty database and import the saved data - - rm inventree_db.sqlite3 + - rm inventree_default_db.sqlite3 - invoke import-records -f data.json after_success: diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 649ded617f..26a3f36aa2 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -40,7 +40,6 @@ class SettingsTest(TestCase): # Check object lookup (case insensitive) self.assertEqual(InvenTreeSetting.get_setting_object('iNvEnTrEE_inSTanCE').pk, 1) - def test_required_values(self): """ - Ensure that every global setting has a name. From 563bfe9bf572b50fb0a1a9880d5bf1690e9d0f89 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 16:10:00 +1100 Subject: [PATCH 42/52] Further fixes to tasks.py --- tasks.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tasks.py b/tasks.py index f4ddf5e0b6..30641c7c81 100644 --- a/tasks.py +++ b/tasks.py @@ -6,6 +6,7 @@ from shutil import copyfile import random import string import os +import sys def apps(): """ @@ -257,11 +258,11 @@ def export_records(c, filename='data.json'): if response not in ['y', 'yes']: print("Cancelled export operation") - return 0 + sys.exit(1) - cmd = f'dumpdata --exclude contenttypes --exclude auth.permission --indent 2 --output {filename}' + cmd = f'dumpdata --exclude contenttypes --exclude auth.permission --indent 2 --output {filename}' - manage(c, cmd, pty=True) + manage(c, cmd, pty=True) @task(help={'filename': 'Input filename'}) def import_records(c, filename='data.json'): @@ -275,7 +276,7 @@ def import_records(c, filename='data.json'): if not os.path.exists(filename): print(f"Error: File '{filename}' does not exist") - return -1 + sys.exit(1) print(f"Importing database records from '{filename}'") From 4765065eb098f530f6e023e858ce48cfe75ddba7 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 16:41:43 +1100 Subject: [PATCH 43/52] Make sure to run database migrations first! (DUH) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 150afdd763..52d0ef1c5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,6 +46,7 @@ script: - invoke export-records -f data.json # Create a new empty database and import the saved data - rm inventree_default_db.sqlite3 + - invoke migrate - invoke import-records -f data.json after_success: From 098804017278a03f41b2e96a21ff1b760b3d620f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 18:04:50 +1100 Subject: [PATCH 44/52] Catch exception where InvenTree setting object is referenced but the database is not migrated yet --- InvenTree/common/models.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index dc53896eaa..53803dd94e 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -16,6 +16,7 @@ from djmoney.models.fields import MoneyField from djmoney.contrib.exchange.models import convert_money from djmoney.contrib.exchange.exceptions import MissingRate +from django.db.utils import OperationalError from django.utils.translation import ugettext as _ from django.core.validators import MinValueValidator from django.core.exceptions import ValidationError @@ -280,12 +281,20 @@ class InvenTreeSetting(models.Model): try: setting = InvenTreeSetting.objects.filter(key__iexact=key).first() - except (InvenTreeSetting.DoesNotExist): - # Create the setting if it does not exist - setting = InvenTreeSetting.create( - key=key, - value=InvenTreeSetting.get_default_value(key) - ) + except OperationalError: + # Settings table has not been created yet! + return None + except (ValueError, InvenTreeSetting.DoesNotExist): + + try: + # Attempt Create the setting if it does not exist + setting = InvenTreeSetting.create( + key=key, + value=InvenTreeSetting.get_default_value(key) + ) + except OperationalError: + # Settings table has not been created yet + setting = None return setting From 6e7224ee7c3ac67fadc85182d57284b3ee9fc77b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 18:05:24 +1100 Subject: [PATCH 45/52] Add "purchase price" field to PurchaseOrderLineItem table --- InvenTree/order/forms.py | 1 + .../migrations/0038_auto_20201112_1737.py | 24 +++++++++++++++++++ InvenTree/order/models.py | 17 ++++++++++--- InvenTree/order/serializers.py | 5 ++++ .../order/purchase_order_detail.html | 8 +++++++ 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 InvenTree/order/migrations/0038_auto_20201112_1737.py diff --git a/InvenTree/order/forms.py b/InvenTree/order/forms.py index 770dd392bf..aa0c897c8a 100644 --- a/InvenTree/order/forms.py +++ b/InvenTree/order/forms.py @@ -175,6 +175,7 @@ class EditPurchaseOrderLineItemForm(HelperForm): 'part', 'quantity', 'reference', + 'purchase_price', 'notes', ] diff --git a/InvenTree/order/migrations/0038_auto_20201112_1737.py b/InvenTree/order/migrations/0038_auto_20201112_1737.py new file mode 100644 index 0000000000..0d563c80d4 --- /dev/null +++ b/InvenTree/order/migrations/0038_auto_20201112_1737.py @@ -0,0 +1,24 @@ +# Generated by Django 3.0.7 on 2020-11-12 06:37 + +from django.db import migrations +import djmoney.models.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0037_auto_20201110_0911'), + ] + + operations = [ + migrations.AddField( + model_name='purchaseorderlineitem', + name='purchase_price', + field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency='USD', help_text='Unit purchase price', max_digits=19, null=True, verbose_name='Purchase Price'), + ), + migrations.AddField( + model_name='purchaseorderlineitem', + name='purchase_price_currency', + field=djmoney.models.fields.CurrencyField(choices=[('AUD', 'Australian Dollar'), ('CAD', 'Canadian Dollar'), ('EUR', 'Euro'), ('NZD', 'New Zealand Dollar'), ('GBP', 'Pound Sterling'), ('USD', 'US Dollar'), ('JPY', 'Yen')], default='USD', editable=False, max_length=3), + ), + ] diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index 359b0cf752..e888ae11c7 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -4,6 +4,10 @@ Order model definitions # -*- coding: utf-8 -*- +import os +from datetime import datetime +from decimal import Decimal + from django.db import models, transaction from django.db.models import F, Sum from django.db.models.functions import Coalesce @@ -15,9 +19,7 @@ from django.utils.translation import ugettext as _ from markdownx.models import MarkdownxField -import os -from datetime import datetime -from decimal import Decimal +from djmoney.models.fields import MoneyField from part import models as PartModels from stock import models as stock_models @@ -499,6 +501,15 @@ class PurchaseOrderLineItem(OrderLineItem): received = models.DecimalField(decimal_places=5, max_digits=15, default=0, help_text=_('Number of items received')) + purchase_price = MoneyField( + max_digits=19, + decimal_places=4, + default_currency='USD', + null=True, + verbose_name=_('Purchase Price'), + help_text=_('Unit purchase price'), + ) + def remaining(self): """ Calculate the number of items remaining to be received """ r = self.quantity - self.received diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index e9838f3347..c91ec6e02d 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -95,6 +95,8 @@ class POLineItemSerializer(InvenTreeModelSerializer): part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True) supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True) + purchase_price_string = serializers.CharField(source='purchase_price', read_only=True) + class Meta: model = PurchaseOrderLineItem @@ -108,6 +110,9 @@ class POLineItemSerializer(InvenTreeModelSerializer): 'part_detail', 'supplier_part_detail', 'received', + 'purchase_price', + 'purchase_price_currency', + 'purchase_price_string', ] diff --git a/InvenTree/order/templates/order/purchase_order_detail.html b/InvenTree/order/templates/order/purchase_order_detail.html index b9eb27d27c..0829eb18e1 100644 --- a/InvenTree/order/templates/order/purchase_order_detail.html +++ b/InvenTree/order/templates/order/purchase_order_detail.html @@ -178,6 +178,14 @@ $("#po-table").inventreeTable({ field: 'quantity', title: '{% trans "Quantity" %}' }, + { + sortable: true, + field: 'purchase_price', + title: '{% trans "Unit Price" %}', + formatter: function(value, row) { + return row.purchase_price_string || row.purchase_price; + } + }, { sortable: true, field: 'received', From 7879c7565e05fe5e6a6ec81403ae40f3b3cf2fbd Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 19:11:12 +1100 Subject: [PATCH 46/52] More fixes to .travis.yml Database in a strange location? --- .readthedocs.yml | 17 ----------------- .travis.yml | 4 ++-- 2 files changed, 2 insertions(+), 19 deletions(-) delete mode 100644 .readthedocs.yml diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index ef696a181b..0000000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,17 +0,0 @@ -# .readthedocs.yml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -version: 2 - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/conf.py - -formats: all - -# Optionally set the version of Python and requirements required to build your docs -python: - version: 3.5 - install: - - requirements: docs/requirements.txt \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 52d0ef1c5c..a65f16b593 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,13 +39,13 @@ script: - invoke translate - invoke style # Create an empty database and fill it with test data - - rm inventree_default_db.sqlite3 + - rm InvenTree/inventree_default_db.sqlite3 - invoke migrate - invoke import-fixtures # Export database records - invoke export-records -f data.json # Create a new empty database and import the saved data - - rm inventree_default_db.sqlite3 + - rm InvenTree/inventree_default_db.sqlite3 - invoke migrate - invoke import-records -f data.json From fd79f1ea0ebbad43c409634e5f14202f41cdaf10 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 19:46:19 +1100 Subject: [PATCH 47/52] Fixes for 'single pricing' for SupplierPart --- InvenTree/company/forms.py | 2 +- InvenTree/company/views.py | 18 ++++++++++++++++-- InvenTree/order/models.py | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/InvenTree/company/forms.py b/InvenTree/company/forms.py index da90286b35..8ee434050f 100644 --- a/InvenTree/company/forms.py +++ b/InvenTree/company/forms.py @@ -79,7 +79,7 @@ class EditSupplierPartForm(HelperForm): single_pricing = MoneyField( label=_('Single Price'), - default_currency=InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY'), + default_currency='USD', help_text=_('Single quantity price'), decimal_places=4, max_digits=19, diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py index 758e8a2990..e82107ec14 100644 --- a/InvenTree/company/views.py +++ b/InvenTree/company/views.py @@ -306,11 +306,14 @@ class SupplierPartCreate(AjaxCreateView): supplier_id = self.get_param('supplier') part_id = self.get_param('part') + supplier = None + if supplier_id: try: - initials['supplier'] = Company.objects.get(pk=supplier_id) + supplier = Company.objects.get(pk=supplier_id) + initials['supplier'] = supplier except (ValueError, Company.DoesNotExist): - pass + supplier = None if manufacturer_id: try: @@ -323,6 +326,17 @@ class SupplierPartCreate(AjaxCreateView): initials['part'] = Part.objects.get(pk=part_id) except (ValueError, Part.DoesNotExist): pass + + # Initial value for single pricing + if supplier: + currency_code = supplier.currency_code + else: + currency_code = common.settings.currency_code_default() + + currency = CURRENCIES.get(currency_code, None) + + if currency_code: + initials['single_pricing'] = ('', currency) return initials diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index e888ae11c7..b2eb59c52e 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -505,7 +505,7 @@ class PurchaseOrderLineItem(OrderLineItem): max_digits=19, decimal_places=4, default_currency='USD', - null=True, + null=True, blank=True, verbose_name=_('Purchase Price'), help_text=_('Unit purchase price'), ) From 534f43872f15ed4680821bd19980f011dbd9cbb7 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 20:14:10 +1100 Subject: [PATCH 48/52] Bug fix for SupplierPart table --- InvenTree/templates/js/company.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/templates/js/company.js b/InvenTree/templates/js/company.js index 164329aee4..388a79668b 100644 --- a/InvenTree/templates/js/company.js +++ b/InvenTree/templates/js/company.js @@ -187,7 +187,7 @@ function loadSupplierPartTable(table, url, options) { field: 'manufacturer', title: '{% trans "Manufacturer" %}', formatter: function(value, row, index, field) { - if (value) { + if (value && row.manufacturer_detail) { var name = row.manufacturer_detail.name; var url = `/company/${value}/`; var html = imageHoverIcon(row.manufacturer_detail.image) + renderLink(name, url); From 47cbf3071d82d09f235344b51c5246ad8788987d Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 21:36:32 +1100 Subject: [PATCH 49/52] Add option to add a single-quantity price-break when creating a new SupplierPart object - Add unit testing! --- InvenTree/company/forms.py | 1 - InvenTree/company/models.py | 19 +++++ InvenTree/company/test_views.py | 115 ++++++++++++++++++++++++++++-- InvenTree/company/views.py | 32 +++++++++ InvenTree/part/fixtures/part.yaml | 1 + 5 files changed, 162 insertions(+), 6 deletions(-) diff --git a/InvenTree/company/forms.py b/InvenTree/company/forms.py index 8ee434050f..0ad95c3e8c 100644 --- a/InvenTree/company/forms.py +++ b/InvenTree/company/forms.py @@ -14,7 +14,6 @@ import django.forms import djmoney.settings from djmoney.forms.fields import MoneyField -from common.models import InvenTreeSetting import common.settings from .models import Company diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 241c5fe1f7..81718a9acd 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -380,6 +380,25 @@ class SupplierPart(models.Model): def unit_pricing(self): return self.get_price(1) + def add_price_break(self, quantity, price): + """ + Create a new price break for this part + + args: + quantity - Numerical quantity + price - Must be a Money object + """ + + # Check if a price break at that quantity already exists... + if self.price_breaks.filter(quantity=quantity, part=self.pk).exists(): + return + + SupplierPriceBreak.objects.create( + part=self, + quantity=quantity, + price=price + ) + def get_price(self, quantity, moq=True, multiples=True, currency=None): """ Calculate the supplier price based on quantity price breaks. diff --git a/InvenTree/company/test_views.py b/InvenTree/company/test_views.py index d895c18957..a68a740d33 100644 --- a/InvenTree/company/test_views.py +++ b/InvenTree/company/test_views.py @@ -3,6 +3,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import json + from django.test import TestCase from django.urls import reverse from django.contrib.auth import get_user_model @@ -11,7 +13,7 @@ from django.contrib.auth.models import Group from .models import SupplierPart -class CompanyViewTest(TestCase): +class CompanyViewTestBase(TestCase): fixtures = [ 'category', @@ -47,14 +49,105 @@ class CompanyViewTest(TestCase): self.client.login(username='username', password='password') - def test_company_index(self): - """ Test the company index """ - response = self.client.get(reverse('company-index')) +class SupplierPartViewTests(CompanyViewTestBase): + """ + Tests for the SupplierPart views. + """ + + def post(self, data, valid=None): + """ + POST against this form and return the response (as a JSON object) + """ + url = reverse('supplier-part-create') + + response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + json_data = json.loads(response.content) + + # If a particular status code is required + if valid is not None: + if valid: + self.assertEqual(json_data['form_valid'], True) + else: + self.assertEqual(json_data['form_valid'], False) + + form_errors = json.loads(json_data['form_errors']) + + return json_data, form_errors + + def test_supplier_part_create(self): + """ + Test the SupplierPartCreate view. + + This view allows some additional functionality, + specifically it allows the user to create a single-quantity price break + automatically, when saving the new SupplierPart model. + """ + + url = reverse('supplier-part-create') + + # First check that we can GET the form + response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + # How many supplier parts are already in the database? + n = SupplierPart.objects.all().count() + + data = { + 'part': 1, + 'supplier': 1, + } + + # SKU is required! (form should fail) + (response, errors) = self.post(data, valid=False) + + self.assertIsNotNone(errors.get('SKU', None)) + + data['SKU'] = 'TEST-ME-123' + + (response, errors) = self.post(data, valid=True) + + # Check that the SupplierPart was created! + self.assertEqual(n + 1, SupplierPart.objects.all().count()) + + # Check that it was created *without* a price-break + supplier_part = SupplierPart.objects.get(pk=response['pk']) + + self.assertEqual(supplier_part.price_breaks.count(), 0) + + # Duplicate SKU is prohibited + (response, errors) = self.post(data, valid=False) + + self.assertIsNotNone(errors.get('__all__', None)) + + # Add with a different SKU, *and* a single-quantity price + data['SKU'] = 'TEST-ME-1234' + data['single_pricing_0'] = '123.4' + data['single_pricing_1'] = 'CAD' + + (response, errors) = self.post(data, valid=True) + + pk = response.get('pk') + + # Check that *another* SupplierPart was created + self.assertEqual(n + 2, SupplierPart.objects.all().count()) + + supplier_part = SupplierPart.objects.get(pk=pk) + + # Check that a price-break has been created! + self.assertEqual(supplier_part.price_breaks.count(), 1) + + price_break = supplier_part.price_breaks.first() + + self.assertEqual(price_break.quantity, 1) + def test_supplier_part_delete(self): - """ Test the SupplierPartDelete view """ + """ + Test the SupplierPartDelete view + """ url = reverse('supplier-part-delete') @@ -80,3 +173,15 @@ class CompanyViewTest(TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(n - 2, SupplierPart.objects.count()) + + +class CompanyViewTest(CompanyViewTestBase): + """ + Tests for various 'Company' views + """ + + def test_company_index(self): + """ Test the company index """ + + response = self.client.get(reverse('company-index')) + self.assertEqual(response.status_code, 200) diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py index e82107ec14..2f734a7cc5 100644 --- a/InvenTree/company/views.py +++ b/InvenTree/company/views.py @@ -271,6 +271,14 @@ class SupplierPartEdit(AjaxUpdateView): ajax_form_title = _('Edit Supplier Part') role_required = 'purchase_order.change' + def get_form(self): + form = super().get_form() + + # Hide the single-pricing field (only for creating a new SupplierPart!) + form.fields['single_pricing'].widget = HiddenInput() + + return form + class SupplierPartCreate(AjaxCreateView): """ Create view for making new SupplierPart """ @@ -282,6 +290,30 @@ class SupplierPartCreate(AjaxCreateView): context_object_name = 'part' role_required = 'purchase_order.add' + def validate(self, part, form): + + single_pricing = form.cleaned_data.get('single_pricing', None) + + if single_pricing: + # TODO - What validation steps can be performed on the single_pricing field? + pass + + def save(self, form): + """ + If single_pricing is defined, add a price break for quantity=1 + """ + + # Save the supplier part object + supplier_part = super().save(form) + + single_pricing = form.cleaned_data.get('single_pricing', None) + + if single_pricing: + + supplier_part.add_price_break(1, single_pricing) + + return supplier_part + def get_form(self): """ Create Form instance to create a new SupplierPart object. Hide some fields if they are not appropriate in context diff --git a/InvenTree/part/fixtures/part.yaml b/InvenTree/part/fixtures/part.yaml index 9883edfcd3..f6d9d246db 100644 --- a/InvenTree/part/fixtures/part.yaml +++ b/InvenTree/part/fixtures/part.yaml @@ -8,6 +8,7 @@ category: 8 link: www.acme.com/parts/m2x4lphs tree_id: 0 + purchaseable: True level: 0 lft: 0 rght: 0 From ae7fbd6112fe11b95b069930398fcf5b02e1c5ab Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 21:53:04 +1100 Subject: [PATCH 50/52] Add PEP8-naming extension for flake - Enforcing python naming checks --- InvenTree/InvenTree/test_api.py | 3 +-- InvenTree/InvenTree/test_views.py | 3 +-- InvenTree/barcode/tests.py | 4 ++-- InvenTree/build/test_build.py | 8 ++++---- InvenTree/build/tests.py | 18 +++++++++--------- InvenTree/common/tests.py | 4 ++-- InvenTree/company/test_api.py | 4 ++-- InvenTree/company/test_views.py | 5 +++-- InvenTree/order/test_api.py | 3 +-- InvenTree/order/test_sales_order.py | 12 ++++++------ InvenTree/order/test_views.py | 3 +-- InvenTree/part/test_api.py | 10 ++++++---- InvenTree/part/test_category.py | 14 +++++++------- InvenTree/part/test_part.py | 24 ++++++++++++------------ InvenTree/part/test_views.py | 5 +++-- InvenTree/stock/api.py | 6 +++--- InvenTree/stock/models.py | 8 ++++---- InvenTree/stock/test_api.py | 5 +++-- InvenTree/stock/test_views.py | 5 +++-- InvenTree/stock/tests.py | 14 +++++++------- requirements.txt | 1 + setup.cfg | 3 +++ 22 files changed, 84 insertions(+), 78 deletions(-) diff --git a/InvenTree/InvenTree/test_api.py b/InvenTree/InvenTree/test_api.py index 69d701def0..f44542656f 100644 --- a/InvenTree/InvenTree/test_api.py +++ b/InvenTree/InvenTree/test_api.py @@ -27,8 +27,7 @@ class APITests(APITestCase): def setUp(self): # Create a user (but do not log in!) - User = get_user_model() - User.objects.create_user(self.username, 'user@email.com', self.password) + get_user_model().objects.create_user(self.username, 'user@email.com', self.password) def basicAuth(self): # Use basic authentication diff --git a/InvenTree/InvenTree/test_views.py b/InvenTree/InvenTree/test_views.py index 171dcbb05f..4f7dddfde4 100644 --- a/InvenTree/InvenTree/test_views.py +++ b/InvenTree/InvenTree/test_views.py @@ -16,8 +16,7 @@ class ViewTests(TestCase): def setUp(self): # Create a user - User = get_user_model() - User.objects.create_user(self.username, 'user@email.com', self.password) + get_user_model().objects.create_user(self.username, 'user@email.com', self.password) self.client.login(username=self.username, password=self.password) diff --git a/InvenTree/barcode/tests.py b/InvenTree/barcode/tests.py index 4b7356aead..5f178d923c 100644 --- a/InvenTree/barcode/tests.py +++ b/InvenTree/barcode/tests.py @@ -24,8 +24,8 @@ class BarcodeAPITest(APITestCase): def setUp(self): # Create a user for auth - User = get_user_model() - User.objects.create_user('testuser', 'test@testing.com', 'password') + user = get_user_model() + user.objects.create_user('testuser', 'test@testing.com', 'password') self.client.login(username='testuser', password='password') diff --git a/InvenTree/build/test_build.py b/InvenTree/build/test_build.py index b560a4f9c9..1b7886c018 100644 --- a/InvenTree/build/test_build.py +++ b/InvenTree/build/test_build.py @@ -267,17 +267,17 @@ class BuildTest(TestCase): # New stock items should have been created! self.assertEqual(StockItem.objects.count(), 4) - A = StockItem.objects.get(pk=self.stock_1_1.pk) + a = StockItem.objects.get(pk=self.stock_1_1.pk) # This stock item has been depleted! with self.assertRaises(StockItem.DoesNotExist): StockItem.objects.get(pk=self.stock_1_2.pk) - C = StockItem.objects.get(pk=self.stock_2_1.pk) + c = StockItem.objects.get(pk=self.stock_2_1.pk) # Stock should have been subtracted from the original items - self.assertEqual(A.quantity, 900) - self.assertEqual(C.quantity, 4500) + self.assertEqual(a.quantity, 900) + self.assertEqual(c.quantity, 4500) # And 10 new stock items created for the build output outputs = StockItem.objects.filter(build=self.build) diff --git a/InvenTree/build/tests.py b/InvenTree/build/tests.py index f01aaa83c9..cb1881e507 100644 --- a/InvenTree/build/tests.py +++ b/InvenTree/build/tests.py @@ -28,10 +28,10 @@ class BuildTestSimple(TestCase): def setUp(self): # Create a user for auth - User = get_user_model() - User.objects.create_user('testuser', 'test@testing.com', 'password') + user = get_user_model() + user.objects.create_user('testuser', 'test@testing.com', 'password') - self.user = User.objects.get(username='testuser') + self.user = user.objects.get(username='testuser') g = Group.objects.create(name='builders') self.user.groups.add(g) @@ -109,11 +109,11 @@ class TestBuildAPI(APITestCase): def setUp(self): # Create a user for auth - User = get_user_model() - user = User.objects.create_user('testuser', 'test@testing.com', 'password') + user = get_user_model() + self.user = user.objects.create_user('testuser', 'test@testing.com', 'password') g = Group.objects.create(name='builders') - user.groups.add(g) + self.user.groups.add(g) for rule in g.rule_sets.all(): if rule.name == 'build': @@ -185,11 +185,11 @@ class TestBuildViews(TestCase): super().setUp() # Create a user - User = get_user_model() - user = User.objects.create_user('username', 'user@email.com', 'password') + user = get_user_model() + self.user = user.objects.create_user('username', 'user@email.com', 'password') g = Group.objects.create(name='builders') - user.groups.add(g) + self.user.groups.add(g) for rule in g.rule_sets.all(): if rule.name == 'build': diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 26a3f36aa2..4666a0a5a6 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -18,9 +18,9 @@ class SettingsTest(TestCase): def setUp(self): - User = get_user_model() + user = get_user_model() - self.user = User.objects.create_user('username', 'user@email.com', 'password') + self.user = user.objects.create_user('username', 'user@email.com', 'password') self.user.is_staff = True self.user.save() diff --git a/InvenTree/company/test_api.py b/InvenTree/company/test_api.py index 643608542d..f466a4a223 100644 --- a/InvenTree/company/test_api.py +++ b/InvenTree/company/test_api.py @@ -15,8 +15,8 @@ class CompanyTest(APITestCase): def setUp(self): # Create a user for auth - User = get_user_model() - self.user = User.objects.create_user('testuser', 'test@testing.com', 'password') + user = get_user_model() + self.user = user.objects.create_user('testuser', 'test@testing.com', 'password') perms = [ 'view_company', diff --git a/InvenTree/company/test_views.py b/InvenTree/company/test_views.py index a68a740d33..951d9d759b 100644 --- a/InvenTree/company/test_views.py +++ b/InvenTree/company/test_views.py @@ -27,8 +27,9 @@ class CompanyViewTestBase(TestCase): super().setUp() # Create a user - User = get_user_model() - self.user = User.objects.create_user( + user = get_user_model() + + self.user = user.objects.create_user( username='username', email='user@email.com', password='password' diff --git a/InvenTree/order/test_api.py b/InvenTree/order/test_api.py index 3a0c48a809..46c6f19277 100644 --- a/InvenTree/order/test_api.py +++ b/InvenTree/order/test_api.py @@ -23,8 +23,7 @@ class OrderTest(APITestCase): def setUp(self): # Create a user for auth - User = get_user_model() - User.objects.create_user('testuser', 'test@testing.com', 'password') + get_user_model().objects.create_user('testuser', 'test@testing.com', 'password') self.client.login(username='testuser', password='password') def doGet(self, url, options=''): diff --git a/InvenTree/order/test_sales_order.py b/InvenTree/order/test_sales_order.py index d40fc4156b..5eb350575e 100644 --- a/InvenTree/order/test_sales_order.py +++ b/InvenTree/order/test_sales_order.py @@ -120,12 +120,12 @@ class SalesOrderTest(TestCase): # There should now be 4 stock items self.assertEqual(StockItem.objects.count(), 4) - Sa = StockItem.objects.get(pk=self.Sa.pk) - Sb = StockItem.objects.get(pk=self.Sb.pk) + sa = StockItem.objects.get(pk=self.Sa.pk) + sb = StockItem.objects.get(pk=self.Sb.pk) # 25 units subtracted from each of the original items - self.assertEqual(Sa.quantity, 75) - self.assertEqual(Sb.quantity, 175) + self.assertEqual(sa.quantity, 75) + self.assertEqual(sb.quantity, 175) # And 2 items created which are associated with the order outputs = StockItem.objects.filter(sales_order=self.order) @@ -134,8 +134,8 @@ class SalesOrderTest(TestCase): for item in outputs.all(): self.assertEqual(item.quantity, 25) - self.assertEqual(Sa.sales_order, None) - self.assertEqual(Sb.sales_order, None) + self.assertEqual(sa.sales_order, None) + self.assertEqual(sb.sales_order, None) # And no allocations self.assertEqual(SalesOrderAllocation.objects.count(), 0) diff --git a/InvenTree/order/test_views.py b/InvenTree/order/test_views.py index 5c4e2dd405..2f014e751f 100644 --- a/InvenTree/order/test_views.py +++ b/InvenTree/order/test_views.py @@ -32,8 +32,7 @@ class OrderViewTestCase(TestCase): super().setUp() # Create a user - User = get_user_model() - user = User.objects.create_user('username', 'user@email.com', 'password') + user = get_user_model().objects.create_user('username', 'user@email.com', 'password') # Ensure that the user has the correct permissions! g = Group.objects.create(name='orders') diff --git a/InvenTree/part/test_api.py b/InvenTree/part/test_api.py index 131936b8bd..e31512e1d8 100644 --- a/InvenTree/part/test_api.py +++ b/InvenTree/part/test_api.py @@ -29,8 +29,9 @@ class PartAPITest(APITestCase): def setUp(self): # Create a user for auth - User = get_user_model() - self.user = User.objects.create_user( + user = get_user_model() + + self.user = user.objects.create_user( username='testuser', email='test@testing.com', password='password' @@ -269,8 +270,9 @@ class PartAPIAggregationTest(APITestCase): def setUp(self): # Create a user for auth - User = get_user_model() - User.objects.create_user('testuser', 'test@testing.com', 'password') + user = get_user_model() + + user.objects.create_user('testuser', 'test@testing.com', 'password') self.client.login(username='testuser', password='password') diff --git a/InvenTree/part/test_category.py b/InvenTree/part/test_category.py index 7fa38d7dcf..b3aa7f1202 100644 --- a/InvenTree/part/test_category.py +++ b/InvenTree/part/test_category.py @@ -165,14 +165,14 @@ class CategoryTest(TestCase): self.assert_equal(p.get_default_location().pathstring, 'Office/Drawer_1') # Any part under electronics should default to 'Home' - R1 = Part.objects.get(name='R_2K2_0805') - self.assertIsNone(R1.default_location) - self.assertEqual(R1.get_default_location().name, 'Home') + r1 = Part.objects.get(name='R_2K2_0805') + self.assertIsNone(r1.default_location) + self.assertEqual(r1.get_default_location().name, 'Home') # But one part has a default_location set - R2 = Part.objects.get(name='R_4K7_0603') - self.assertEqual(R2.get_default_location().name, 'Bathroom') + r2 = Part.objects.get(name='R_4K7_0603') + self.assertEqual(r2.get_default_location().name, 'Bathroom') # And one part should have no default location at all - W = Part.objects.get(name='Widget') - self.assertIsNone(W.get_default_location()) + w = Part.objects.get(name='Widget') + self.assertIsNone(w.get_default_location()) diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index 677b159762..c02be211b5 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -54,10 +54,10 @@ class PartTest(TestCase): ] def setUp(self): - self.R1 = Part.objects.get(name='R_2K2_0805') - self.R2 = Part.objects.get(name='R_4K7_0603') + self.r1 = Part.objects.get(name='R_2K2_0805') + self.r2 = Part.objects.get(name='R_4K7_0603') - self.C1 = Part.objects.get(name='C_22N_0805') + self.c1 = Part.objects.get(name='C_22N_0805') Part.objects.rebuild() @@ -78,18 +78,18 @@ class PartTest(TestCase): self.assertEqual(str(p), "BOB | Bob | A2 - Can we build it?") def test_metadata(self): - self.assertEqual(self.R1.name, 'R_2K2_0805') - self.assertEqual(self.R1.get_absolute_url(), '/part/3/') + self.assertEqual(self.r1.name, 'R_2K2_0805') + self.assertEqual(self.r1.get_absolute_url(), '/part/3/') def test_category(self): - self.assertEqual(str(self.C1.category), 'Electronics/Capacitors - Capacitors') + self.assertEqual(str(self.c1.category), 'Electronics/Capacitors - Capacitors') orphan = Part.objects.get(name='Orphan') self.assertIsNone(orphan.category) self.assertEqual(orphan.category_path, '') def test_rename_img(self): - img = rename_part_image(self.R1, 'hello.png') + img = rename_part_image(self.r1, 'hello.png') self.assertEqual(img, os.path.join('part_images', 'hello.png')) def test_stock(self): @@ -100,12 +100,12 @@ class PartTest(TestCase): self.assertEqual(r.available_stock, 0) def test_barcode(self): - barcode = self.R1.format_barcode() + barcode = self.r1.format_barcode() self.assertIn('InvenTree', barcode) - self.assertIn(self.R1.name, barcode) + self.assertIn(self.r1.name, barcode) def test_copy(self): - self.R2.deep_copy(self.R1, image=True, bom=True) + self.r2.deep_copy(self.r1, image=True, bom=True) def test_match_names(self): @@ -181,9 +181,9 @@ class PartSettingsTest(TestCase): def setUp(self): # Create a user for auth - User = get_user_model() + user = get_user_model() - self.user = User.objects.create_user( + self.user = user.objects.create_user( username='testuser', email='test@testing.com', password='password', diff --git a/InvenTree/part/test_views.py b/InvenTree/part/test_views.py index 2a36dd9012..13349aae15 100644 --- a/InvenTree/part/test_views.py +++ b/InvenTree/part/test_views.py @@ -23,8 +23,9 @@ class PartViewTestCase(TestCase): super().setUp() # Create a user - User = get_user_model() - self.user = User.objects.create_user( + user = get_user_model() + + self.user = user.objects.create_user( username='username', email='user@email.com', password='password' diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index dc16fb52a6..b74ac200f7 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -617,10 +617,10 @@ class StockList(generics.ListCreateAPIView): queryset = queryset.exclude(quantity__lte=0) # Filter by internal part number - IPN = params.get('IPN', None) + ipn = params.get('IPN', None) - if IPN is not None: - queryset = queryset.filter(part__IPN=IPN) + if ipn is not None: + queryset = queryset.filter(part__IPN=ipn) # Does the client wish to filter by the Part ID? part_id = params.get('part', None) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 9e31774946..01b7b27cc4 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -621,12 +621,12 @@ class StockItem(MPTTModel): return self.installedItemCount() > 0 @transaction.atomic - def installStockItem(self, otherItem, quantity, user, notes): + def installStockItem(self, other_item, quantity, user, notes): """ Install another stock item into this stock item. Args - otherItem: The stock item to install into this stock item + other_item: The stock item to install into this stock item quantity: The quantity of stock to install user: The user performing the operation notes: Any notes associated with the operation @@ -637,10 +637,10 @@ class StockItem(MPTTModel): return False # If the quantity is less than the stock item, split the stock! - stock_item = otherItem.splitStock(quantity, None, user) + stock_item = other_item.splitStock(quantity, None, user) if stock_item is None: - stock_item = otherItem + stock_item = other_item # Assign the other stock item into this one stock_item.belongs_to = self diff --git a/InvenTree/stock/test_api.py b/InvenTree/stock/test_api.py index 45a1669535..a34e895ed8 100644 --- a/InvenTree/stock/test_api.py +++ b/InvenTree/stock/test_api.py @@ -22,8 +22,9 @@ class StockAPITestCase(APITestCase): def setUp(self): # Create a user for auth - User = get_user_model() - self.user = User.objects.create_user('testuser', 'test@testing.com', 'password') + user = get_user_model() + + self.user = user.objects.create_user('testuser', 'test@testing.com', 'password') # Add the necessary permissions to the user perms = [ diff --git a/InvenTree/stock/test_views.py b/InvenTree/stock/test_views.py index 2245aff17d..1f55d74eec 100644 --- a/InvenTree/stock/test_views.py +++ b/InvenTree/stock/test_views.py @@ -23,8 +23,9 @@ class StockViewTestCase(TestCase): super().setUp() # Create a user - User = get_user_model() - self.user = User.objects.create_user( + user = get_user_model() + + self.user = user.objects.create_user( username='username', email='user@email.com', password='password' diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index 1bd4feb02f..3d309c0360 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -38,12 +38,12 @@ class StockTest(TestCase): self.drawer3 = StockLocation.objects.get(name='Drawer_3') # Create a user - User = get_user_model() - User.objects.create_user('username', 'user@email.com', 'password') + user = get_user_model() + user.objects.create_user('username', 'user@email.com', 'password') self.client.login(username='username', password='password') - self.user = User.objects.get(username='username') + self.user = user.objects.get(username='username') # Ensure the MPTT objects are correctly rebuild Part.objects.rebuild() @@ -221,21 +221,21 @@ class StockTest(TestCase): def test_split_stock(self): # Split the 1234 x 2K2 resistors in Drawer_1 - N = StockItem.objects.filter(part=3).count() + n = StockItem.objects.filter(part=3).count() stock = StockItem.objects.get(id=1234) stock.splitStock(1000, None, self.user) self.assertEqual(stock.quantity, 234) # There should be a new stock item too! - self.assertEqual(StockItem.objects.filter(part=3).count(), N + 1) + self.assertEqual(StockItem.objects.filter(part=3).count(), n + 1) # Try to split a negative quantity stock.splitStock(-10, None, self.user) - self.assertEqual(StockItem.objects.filter(part=3).count(), N + 1) + self.assertEqual(StockItem.objects.filter(part=3).count(), n + 1) stock.splitStock(stock.quantity, None, self.user) - self.assertEqual(StockItem.objects.filter(part=3).count(), N + 1) + self.assertEqual(StockItem.objects.filter(part=3).count(), n + 1) def test_stocktake(self): # Perform stocktake diff --git a/requirements.txt b/requirements.txt index 5de97d3be5..76610600a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,6 +18,7 @@ django-import-export==2.0.0 # Data import / export for admin interface django-cleanup==4.0.0 # Manage deletion of old / unused uploaded files django-qr-code==1.2.0 # Generate QR codes flake8==3.8.3 # PEP checking +pep8-naming==0.11.1 # PEP naming convention extension coverage==5.3 # Unit test coverage coveralls==2.1.2 # Coveralls linking (for Travis) rapidfuzz==0.7.6 # Fuzzy string matching diff --git a/setup.cfg b/setup.cfg index ca36d114b6..6e2a44f055 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,5 +7,8 @@ ignore = E501, E722, # - C901 - function is too complex C901, + # - N802 - function name should be lowercase (In the future, we should conform to this!) + N802, + N812, exclude = .git,__pycache__,*/migrations/*,*/lib/*,*/bin/*,*/media/*,*/static/*,*ci_*.py* max-complexity = 20 From f239c8f8c831001feeb46acc28af7a8fd5871b60 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 22:04:50 +1100 Subject: [PATCH 51/52] Add missing migration file --- .travis.yml | 4 +- InvenTree/company/test_views.py | 42 +++++++++++++------ .../migrations/0039_auto_20201112_2203.py | 19 +++++++++ 3 files changed, 51 insertions(+), 14 deletions(-) create mode 100644 InvenTree/order/migrations/0039_auto_20201112_2203.py diff --git a/.travis.yml b/.travis.yml index a65f16b593..52d0ef1c5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,13 +39,13 @@ script: - invoke translate - invoke style # Create an empty database and fill it with test data - - rm InvenTree/inventree_default_db.sqlite3 + - rm inventree_default_db.sqlite3 - invoke migrate - invoke import-fixtures # Export database records - invoke export-records -f data.json # Create a new empty database and import the saved data - - rm InvenTree/inventree_default_db.sqlite3 + - rm inventree_default_db.sqlite3 - invoke migrate - invoke import-records -f data.json diff --git a/InvenTree/company/test_views.py b/InvenTree/company/test_views.py index 951d9d759b..9fbe0e01f5 100644 --- a/InvenTree/company/test_views.py +++ b/InvenTree/company/test_views.py @@ -50,17 +50,10 @@ class CompanyViewTestBase(TestCase): self.client.login(username='username', password='password') - -class SupplierPartViewTests(CompanyViewTestBase): - """ - Tests for the SupplierPart views. - """ - - def post(self, data, valid=None): + def post(self, url, data, valid=None): """ POST against this form and return the response (as a JSON object) """ - url = reverse('supplier-part-create') response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') @@ -79,6 +72,14 @@ class SupplierPartViewTests(CompanyViewTestBase): return json_data, form_errors + + +class SupplierPartViewTests(CompanyViewTestBase): + """ + Tests for the SupplierPart views. + """ + + def test_supplier_part_create(self): """ Test the SupplierPartCreate view. @@ -103,13 +104,13 @@ class SupplierPartViewTests(CompanyViewTestBase): } # SKU is required! (form should fail) - (response, errors) = self.post(data, valid=False) + (response, errors) = self.post(url, data, valid=False) self.assertIsNotNone(errors.get('SKU', None)) data['SKU'] = 'TEST-ME-123' - (response, errors) = self.post(data, valid=True) + (response, errors) = self.post(url, data, valid=True) # Check that the SupplierPart was created! self.assertEqual(n + 1, SupplierPart.objects.all().count()) @@ -120,7 +121,7 @@ class SupplierPartViewTests(CompanyViewTestBase): self.assertEqual(supplier_part.price_breaks.count(), 0) # Duplicate SKU is prohibited - (response, errors) = self.post(data, valid=False) + (response, errors) = self.post(url, data, valid=False) self.assertIsNotNone(errors.get('__all__', None)) @@ -129,7 +130,7 @@ class SupplierPartViewTests(CompanyViewTestBase): data['single_pricing_0'] = '123.4' data['single_pricing_1'] = 'CAD' - (response, errors) = self.post(data, valid=True) + (response, errors) = self.post(url, data, valid=True) pk = response.get('pk') @@ -186,3 +187,20 @@ class CompanyViewTest(CompanyViewTestBase): response = self.client.get(reverse('company-index')) self.assertEqual(response.status_code, 200) + + def test_company_create(self): + """ + Test the view for creating a company + """ + + url = reverse('company-create') + + # Check that different company types return different form titles + response = self.client.get(reverse('supplier-create'), HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertContains(response, 'Create new Supplier') + + response = self.client.get(reverse('manufacturer-create'), HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertContains(response, 'Create new Manufacturer') + + response = self.client.get(reverse('customer-create'), HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertContains(response, 'Create new Customer') diff --git a/InvenTree/order/migrations/0039_auto_20201112_2203.py b/InvenTree/order/migrations/0039_auto_20201112_2203.py new file mode 100644 index 0000000000..bff585688d --- /dev/null +++ b/InvenTree/order/migrations/0039_auto_20201112_2203.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.7 on 2020-11-12 11:03 + +from django.db import migrations +import djmoney.models.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0038_auto_20201112_1737'), + ] + + operations = [ + migrations.AlterField( + model_name='purchaseorderlineitem', + name='purchase_price', + field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency='USD', help_text='Unit purchase price', max_digits=19, null=True, verbose_name='Purchase Price'), + ), + ] From 362437e75ede7fd25f23e3ec5e87db811c070760 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 13 Nov 2020 07:28:21 +1100 Subject: [PATCH 52/52] PEP fixes --- InvenTree/company/test_views.py | 4 - InvenTree/locale/de/LC_MESSAGES/django.po | 1002 ++++++++++++--------- InvenTree/locale/en/LC_MESSAGES/django.po | 956 +++++++++++--------- InvenTree/locale/es/LC_MESSAGES/django.po | 956 +++++++++++--------- 4 files changed, 1652 insertions(+), 1266 deletions(-) diff --git a/InvenTree/company/test_views.py b/InvenTree/company/test_views.py index 9fbe0e01f5..0163e65c29 100644 --- a/InvenTree/company/test_views.py +++ b/InvenTree/company/test_views.py @@ -73,13 +73,11 @@ class CompanyViewTestBase(TestCase): return json_data, form_errors - class SupplierPartViewTests(CompanyViewTestBase): """ Tests for the SupplierPart views. """ - def test_supplier_part_create(self): """ Test the SupplierPartCreate view. @@ -193,8 +191,6 @@ class CompanyViewTest(CompanyViewTestBase): Test the view for creating a company """ - url = reverse('company-create') - # Check that different company types return different form titles response = self.client.get(reverse('supplier-create'), HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertContains(response, 'Create new Supplier') diff --git a/InvenTree/locale/de/LC_MESSAGES/django.po b/InvenTree/locale/de/LC_MESSAGES/django.po index 242061af16..e7f273efc2 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-11-10 13:31+0000\n" +"POT-Creation-Date: 2020-11-12 22:05+1100\n" "PO-Revision-Date: 2020-05-03 11:32+0200\n" "Last-Translator: Christian Schlüter \n" "Language-Team: C \n" @@ -25,31 +25,37 @@ msgstr "Keine Aktion angegeben" msgid "No matching action found" msgstr "Keine passende Aktion gefunden" -#: InvenTree/forms.py:107 build/forms.py:82 build/forms.py:170 +#: InvenTree/forms.py:108 build/forms.py:82 build/forms.py:170 msgid "Confirm" msgstr "Bestätigen" -#: InvenTree/forms.py:123 +#: InvenTree/forms.py:124 #, fuzzy #| msgid "Confim BOM item deletion" msgid "Confirm item deletion" msgstr "Löschung von BOM-Position bestätigen" -#: InvenTree/forms.py:155 +#: InvenTree/forms.py:156 #, fuzzy #| msgid "Create new part" msgid "Enter new password" msgstr "Neues Teil anlegen" -#: InvenTree/forms.py:162 +#: InvenTree/forms.py:163 msgid "Confirm new password" msgstr "" -#: InvenTree/forms.py:197 +#: InvenTree/forms.py:198 msgid "Apply Theme" msgstr "" -#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:269 +#: InvenTree/forms.py:228 +#, fuzzy +#| msgid "Set Part Category" +msgid "Select Category" +msgstr "Teilkategorie auswählen" + +#: InvenTree/helpers.py:361 order/models.py:189 order/models.py:271 msgid "Invalid quantity provided" msgstr "Keine gültige Menge" @@ -107,19 +113,19 @@ msgstr "Name" msgid "Description (optional)" msgstr "Firmenbeschreibung" -#: InvenTree/settings.py:350 +#: InvenTree/settings.py:354 msgid "English" msgstr "Englisch" -#: InvenTree/settings.py:351 +#: InvenTree/settings.py:355 msgid "German" msgstr "Deutsch" -#: InvenTree/settings.py:352 +#: InvenTree/settings.py:356 msgid "French" msgstr "Französisch" -#: InvenTree/settings.py:353 +#: InvenTree/settings.py:357 msgid "Polish" msgstr "Polnisch" @@ -182,67 +188,71 @@ msgstr "" msgid "Production" msgstr "Standort" -#: InvenTree/validators.py:39 +#: InvenTree/validators.py:22 +msgid "Not a valid currency code" +msgstr "" + +#: InvenTree/validators.py:50 msgid "Invalid character in part name" msgstr "Ungültiger Buchstabe im Teilenamen" -#: InvenTree/validators.py:52 +#: InvenTree/validators.py:63 msgid "IPN must match regex pattern" msgstr "IPN muss zu Regex-Muster passen" -#: InvenTree/validators.py:66 InvenTree/validators.py:80 -#: InvenTree/validators.py:94 +#: InvenTree/validators.py:77 InvenTree/validators.py:91 +#: InvenTree/validators.py:105 #, fuzzy #| msgid "IPN must match regex pattern" msgid "Reference must match pattern" msgstr "IPN muss zu Regex-Muster passen" -#: InvenTree/validators.py:102 +#: InvenTree/validators.py:113 #, python-brace-format msgid "Illegal character in name ({x})" msgstr "Ungültiges Zeichen im Namen ({x})" -#: InvenTree/validators.py:121 InvenTree/validators.py:137 +#: InvenTree/validators.py:132 InvenTree/validators.py:148 msgid "Overage value must not be negative" msgstr "Überschuss-Wert darf nicht negativ sein" -#: InvenTree/validators.py:139 +#: InvenTree/validators.py:150 msgid "Overage must not exceed 100%" msgstr "Überschuss darf 100% nicht überschreiten" -#: InvenTree/validators.py:146 +#: InvenTree/validators.py:157 msgid "Overage must be an integer value or a percentage" msgstr "Überschuss muss eine Ganzzahl oder ein Prozentwert sein" -#: InvenTree/views.py:493 +#: InvenTree/views.py:494 #, fuzzy #| msgid "Delete BOM Item" msgid "Delete Item" msgstr "BOM-Position löschen" -#: InvenTree/views.py:542 +#: InvenTree/views.py:543 #, fuzzy #| msgid "Confim BOM item deletion" msgid "Check box to confirm item deletion" msgstr "Löschung von BOM-Position bestätigen" -#: InvenTree/views.py:557 templates/InvenTree/settings/user.html:18 +#: InvenTree/views.py:558 templates/InvenTree/settings/user.html:18 #, fuzzy #| msgid "No user information" msgid "Edit User Information" msgstr "Keine Benutzerinformation" -#: InvenTree/views.py:568 templates/InvenTree/settings/user.html:22 +#: InvenTree/views.py:569 templates/InvenTree/settings/user.html:22 #, fuzzy #| msgid "Select part" msgid "Set Password" msgstr "Teil auswählen" -#: InvenTree/views.py:587 +#: InvenTree/views.py:588 msgid "Password fields must match" msgstr "" -#: InvenTree/views.py:757 +#: InvenTree/views.py:794 msgid "Database Statistics" msgstr "Datenbankstatistiken" @@ -298,10 +308,10 @@ msgstr "Bestell-Referenz" #: build/forms.py:70 build/templates/build/auto_allocate.html:17 #: build/templates/build/build_base.html:78 -#: build/templates/build/detail.html:29 -#: company/templates/company/supplier_part_pricing.html:75 +#: build/templates/build/detail.html:29 common/models.py:488 +#: company/forms.py:112 company/templates/company/supplier_part_pricing.html:75 #: order/templates/order/order_wizard/select_parts.html:32 -#: order/templates/order/purchase_order_detail.html:178 +#: order/templates/order/purchase_order_detail.html:179 #: order/templates/order/sales_order_detail.html:74 #: order/templates/order/sales_order_detail.html:156 #: part/templates/part/allocation.html:16 @@ -397,7 +407,7 @@ msgstr "Bauauftrag" #: build/models.py:57 build/templates/build/index.html:6 #: build/templates/build/index.html:14 order/templates/order/so_builds.html:11 #: order/templates/order/so_tabs.html:9 part/templates/part/tabs.html:31 -#: templates/InvenTree/settings/tabs.html:25 users/models.py:30 +#: templates/InvenTree/settings/tabs.html:28 users/models.py:30 msgid "Build Orders" msgstr "Bauaufträge" @@ -407,19 +417,20 @@ msgstr "Bauaufträge" msgid "Build Order Reference" msgstr "Bestellreferenz" -#: build/models.py:73 order/templates/order/purchase_order_detail.html:173 +#: build/models.py:73 order/templates/order/purchase_order_detail.html:174 #: templates/js/bom.js:181 templates/js/build.js:493 msgid "Reference" msgstr "Referenz" #: build/models.py:80 build/templates/build/detail.html:19 +#: company/templates/company/detail.html:23 #: company/templates/company/supplier_part_base.html:61 #: company/templates/company/supplier_part_detail.html:27 -#: order/templates/order/purchase_order_detail.html:160 +#: order/templates/order/purchase_order_detail.html:161 #: part/templates/part/detail.html:51 part/templates/part/set_category.html:14 #: templates/InvenTree/search.html:147 templates/js/bom.js:174 #: templates/js/bom.js:499 templates/js/build.js:642 templates/js/company.js:56 -#: templates/js/order.js:167 templates/js/order.js:249 templates/js/part.js:188 +#: templates/js/order.js:168 templates/js/order.js:250 templates/js/part.js:188 #: templates/js/part.js:271 templates/js/part.js:391 templates/js/part.js:572 #: templates/js/stock.js:494 templates/js/stock.js:706 msgid "Description" @@ -442,10 +453,10 @@ msgstr "Bestellung, die diesem Bau zugwiesen ist" #: build/models.py:97 build/templates/build/auto_allocate.html:16 #: build/templates/build/build_base.html:73 -#: build/templates/build/detail.html:24 order/models.py:519 +#: build/templates/build/detail.html:24 order/models.py:530 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:148 -#: order/templates/order/receive_parts.html:19 part/models.py:294 +#: order/templates/order/receive_parts.html:19 part/models.py:315 #: part/templates/part/part_app_base.html:7 part/templates/part/related.html:26 #: part/templates/part/set_category.html:13 templates/InvenTree/search.html:133 #: templates/js/barcode.js:336 templates/js/bom.js:147 templates/js/bom.js:484 @@ -535,13 +546,13 @@ msgstr "Chargennummer für diese Bau-Ausgabe" msgid "External Link" msgstr "Externer Link" -#: build/models.py:177 part/models.py:609 stock/models.py:386 +#: build/models.py:177 part/models.py:672 stock/models.py:386 msgid "Link to external URL" msgstr "Link zu einer externen URL" -#: build/models.py:181 build/templates/build/tabs.html:23 company/models.py:314 +#: build/models.py:181 build/templates/build/tabs.html:23 company/models.py:344 #: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:18 -#: order/templates/order/purchase_order_detail.html:203 +#: order/templates/order/purchase_order_detail.html:213 #: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:73 #: stock/forms.py:307 stock/forms.py:339 stock/forms.py:367 stock/models.py:448 #: stock/models.py:1432 stock/templates/stock/tabs.html:26 @@ -597,11 +608,11 @@ msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" msgstr "" "zugewiesene Anzahl ({n}) darf nicht die verfügbare ({q}) Anzahl überschreiten" -#: build/models.py:908 order/models.py:603 +#: build/models.py:908 order/models.py:614 msgid "StockItem is over-allocated" msgstr "Zu viele Lagerobjekte zugewiesen" -#: build/models.py:912 order/models.py:606 +#: build/models.py:912 order/models.py:617 msgid "Allocation quantity must be greater than zero" msgstr "Anzahl muss größer null sein" @@ -779,7 +790,7 @@ msgstr "Bau-Status" #: order/templates/order/receive_parts.html:24 #: stock/templates/stock/item_base.html:312 templates/InvenTree/search.html:175 #: templates/js/barcode.js:42 templates/js/build.js:675 -#: templates/js/order.js:172 templates/js/order.js:254 +#: templates/js/order.js:173 templates/js/order.js:255 #: templates/js/stock.js:557 templates/js/stock.js:961 msgid "Status" msgstr "Status" @@ -790,13 +801,13 @@ msgid "Progress" msgstr "" #: build/templates/build/build_base.html:101 -#: build/templates/build/detail.html:82 order/models.py:517 +#: build/templates/build/detail.html:82 order/models.py:528 #: order/templates/order/sales_order_base.html:9 #: order/templates/order/sales_order_base.html:33 #: order/templates/order/sales_order_notes.html:10 #: order/templates/order/sales_order_ship.html:25 #: part/templates/part/allocation.html:27 -#: stock/templates/stock/item_base.html:221 templates/js/order.js:221 +#: stock/templates/stock/item_base.html:221 templates/js/order.js:222 msgid "Sales Order" msgstr "Bestellung" @@ -1035,7 +1046,7 @@ msgstr "Lagerbestand dem Bau zuweisen" msgid "Create Build Output" msgstr "Bau-Ausgabe" -#: build/views.py:207 stock/models.py:827 stock/views.py:1647 +#: build/views.py:207 stock/models.py:827 stock/views.py:1660 #, fuzzy #| msgid "Serial numbers already exist: " msgid "Serial numbers already exist" @@ -1057,7 +1068,7 @@ msgstr "Bau entfernt" msgid "Confirm unallocation of build stock" msgstr "Zuweisungsaufhebung bestätigen" -#: build/views.py:303 build/views.py:388 stock/views.py:413 +#: build/views.py:303 build/views.py:388 stock/views.py:417 msgid "Check the confirmation box" msgstr "Bestätigungsbox bestätigen" @@ -1174,8 +1185,8 @@ msgstr "Bauobjekt aktualisiert" msgid "Add Build Order Attachment" msgstr "Auftragsanhang hinzufügen" -#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:166 -#: stock/views.py:176 +#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:168 +#: stock/views.py:180 msgid "Added attachment" msgstr "Anhang hinzugefügt" @@ -1191,205 +1202,221 @@ msgstr "Anhang aktualisiert" msgid "Delete Attachment" msgstr "Anhang löschen" -#: build/views.py:1122 order/views.py:240 order/views.py:255 stock/views.py:234 +#: build/views.py:1122 order/views.py:240 order/views.py:255 stock/views.py:238 msgid "Deleted attachment" msgstr "Anhang gelöscht" -#: common/models.py:55 +#: common/models.py:56 #, fuzzy #| msgid "Instance Name" msgid "InvenTree Instance Name" msgstr "Instanzname" -#: common/models.py:57 +#: common/models.py:58 #, fuzzy #| msgid "Brief description of the build" msgid "String descriptor for the server instance" msgstr "Kurze Beschreibung des Baus" -#: common/models.py:61 company/models.py:89 company/models.py:90 +#: common/models.py:62 company/models.py:95 company/models.py:96 msgid "Company name" msgstr "Firmenname" -#: common/models.py:62 +#: common/models.py:63 #, fuzzy #| msgid "Company name" msgid "Internal company name" msgstr "Firmenname" -#: common/models.py:67 +#: common/models.py:68 #, fuzzy #| msgid "Delete Currency" msgid "Default Currency" msgstr "Währung entfernen" -#: common/models.py:68 +#: common/models.py:69 #, fuzzy #| msgid "Delete Currency" msgid "Default currency" msgstr "Währung entfernen" -#: common/models.py:74 +#: common/models.py:75 msgid "IPN Regex" msgstr "" -#: common/models.py:75 +#: common/models.py:76 msgid "Regular expression pattern for matching Part IPN" msgstr "" -#: common/models.py:79 +#: common/models.py:80 #, fuzzy #| msgid "Duplicate Part" msgid "Allow Duplicate IPN" msgstr "Teil duplizieren" -#: common/models.py:80 +#: common/models.py:81 msgid "Allow multiple parts to share the same IPN" msgstr "" -#: common/models.py:86 +#: common/models.py:87 #, fuzzy #| msgid "Import BOM data" msgid "Copy Part BOM Data" msgstr "Stückliste importieren" -#: common/models.py:87 +#: common/models.py:88 msgid "Copy BOM data by default when duplicating a part" msgstr "" -#: common/models.py:93 +#: common/models.py:94 #, fuzzy #| msgid "Parameters" msgid "Copy Part Parameter Data" msgstr "Parameter" -#: common/models.py:94 +#: common/models.py:95 msgid "Copy parameter data by default when duplicating a part" msgstr "" -#: common/models.py:100 +#: common/models.py:101 #, fuzzy #| msgid "Parameters" msgid "Copy Part Test Data" msgstr "Parameter" -#: common/models.py:101 +#: common/models.py:102 msgid "Copy test data by default when duplicating a part" msgstr "" -#: common/models.py:107 part/models.py:680 part/templates/part/detail.html:168 +#: common/models.py:108 +#, fuzzy +#| msgid "Edit Part Parameter Template" +msgid "Copy Category Parameter Templates" +msgstr "Teilparametervorlage bearbeiten" + +#: common/models.py:109 +msgid "Copy category parameter templates when creating a part" +msgstr "" + +#: common/models.py:115 part/models.py:743 part/templates/part/detail.html:168 #: templates/js/table_filters.js:264 msgid "Component" msgstr "Komponente" -#: common/models.py:108 +#: common/models.py:116 #, fuzzy #| msgid "Part can be used in assemblies" msgid "Parts can be used as sub-components by default" msgstr "Teil kann in Baugruppen benutzt werden" -#: common/models.py:114 part/models.py:691 part/templates/part/detail.html:188 +#: common/models.py:122 part/models.py:754 part/templates/part/detail.html:188 msgid "Purchaseable" msgstr "Kaufbar" -#: common/models.py:115 +#: common/models.py:123 msgid "Parts are purchaseable by default" msgstr "" -#: common/models.py:121 part/models.py:696 part/templates/part/detail.html:198 +#: common/models.py:129 part/models.py:759 part/templates/part/detail.html:198 #: templates/js/table_filters.js:272 msgid "Salable" msgstr "Verkäuflich" -#: common/models.py:122 +#: common/models.py:130 msgid "Parts are salable by default" msgstr "" -#: common/models.py:128 part/models.py:686 part/templates/part/detail.html:178 +#: common/models.py:136 part/models.py:749 part/templates/part/detail.html:178 #: templates/js/table_filters.js:31 templates/js/table_filters.js:276 msgid "Trackable" msgstr "nachverfolgbar" -#: common/models.py:129 +#: common/models.py:137 msgid "Parts are trackable by default" msgstr "" -#: common/models.py:135 +#: common/models.py:143 #, fuzzy #| msgid "Order Reference" msgid "Build Order Reference Prefix" msgstr "Bestellreferenz" -#: common/models.py:136 +#: common/models.py:144 #, fuzzy #| msgid "Order reference" msgid "Prefix value for build order reference" msgstr "Bestell-Referenz" -#: common/models.py:141 +#: common/models.py:149 #, fuzzy #| msgid "Order Reference" msgid "Build Order Reference Regex" msgstr "Bestellreferenz" -#: common/models.py:142 +#: common/models.py:150 msgid "Regular expression pattern for matching build order reference" msgstr "" -#: common/models.py:146 +#: common/models.py:154 #, fuzzy #| msgid "Sales Order Reference" msgid "Sales Order Reference Prefix" msgstr "Bestellungsreferenz" -#: common/models.py:147 +#: common/models.py:155 #, fuzzy #| msgid "Order reference" msgid "Prefix value for sales order reference" msgstr "Bestell-Referenz" -#: common/models.py:151 +#: common/models.py:159 #, fuzzy #| msgid "Order reference" msgid "Purchase Order Reference Prefix" msgstr "Bestell-Referenz" -#: common/models.py:152 +#: common/models.py:160 #, fuzzy #| msgid "Order reference" msgid "Prefix value for purchase order reference" msgstr "Bestell-Referenz" -#: common/models.py:357 +#: common/models.py:373 msgid "Settings key (must be unique - case insensitive" msgstr "" "Einstellungs-Schlüssel (muss einzigartig sein, Groß-/ Kleinschreibung wird " "nicht beachtet)" -#: common/models.py:359 +#: common/models.py:375 msgid "Settings value" msgstr "Einstellungs-Wert" -#: common/models.py:415 +#: common/models.py:431 msgid "Value must be a boolean value" msgstr "" -#: common/models.py:429 +#: common/models.py:445 msgid "Key string must be unique" msgstr "Schlüsseltext muss eindeutig sein" -#: common/models.py:474 company/templates/company/supplier_part_pricing.html:80 +#: common/models.py:489 company/forms.py:113 +#, fuzzy +#| msgid "Price Breaks" +msgid "Price break quantity" +msgstr "Preisstaffelung" + +#: common/models.py:497 company/templates/company/supplier_part_pricing.html:80 #: part/templates/part/sale_prices.html:87 templates/js/bom.js:234 msgid "Price" msgstr "Preis" -#: common/models.py:475 +#: common/models.py:498 #, fuzzy #| msgid "Enter a valid quantity" msgid "Unit price at specified quantity" msgstr "Bitte eine gültige Anzahl eingeben" -#: common/models.py:498 +#: common/models.py:521 #, fuzzy #| msgid "Default Location" msgid "Default" @@ -1407,113 +1434,137 @@ msgstr "Währungs-Wert" msgid "Change Setting" msgstr "Einstellungen" -#: company/models.py:92 +#: company/forms.py:37 company/models.py:139 +#, fuzzy +#| msgid "Do you purchase items from this company?" +msgid "Default currency used for this company" +msgstr "Kaufen Sie Teile von dieser Firma?" + +#: company/forms.py:80 +#, fuzzy +#| msgid "Price" +msgid "Single Price" +msgstr "Preis" + +#: company/forms.py:82 +#, fuzzy +#| msgid "Invalid quantity provided" +msgid "Single quantity price" +msgstr "Keine gültige Menge" + +#: company/models.py:98 #, fuzzy #| msgid "Part description" msgid "Company description" msgstr "Beschreibung des Teils" -#: company/models.py:92 +#: company/models.py:98 msgid "Description of the company" msgstr "Firmenbeschreibung" -#: company/models.py:94 company/templates/company/company_base.html:57 -#: templates/js/company.js:61 +#: company/models.py:100 company/templates/company/company_base.html:57 +#: company/templates/company/detail.html:28 templates/js/company.js:61 msgid "Website" msgstr "Website" -#: company/models.py:94 +#: company/models.py:100 msgid "Company website URL" msgstr "Firmenwebsite" -#: company/models.py:97 company/templates/company/company_base.html:64 +#: company/models.py:103 company/templates/company/company_base.html:64 msgid "Address" msgstr "Adresse" -#: company/models.py:98 +#: company/models.py:104 msgid "Company address" msgstr "Firmenadresse" -#: company/models.py:101 +#: company/models.py:107 #, fuzzy #| msgid "Contact phone number" msgid "Phone number" msgstr "Kontakt-Tel." -#: company/models.py:102 +#: company/models.py:108 msgid "Contact phone number" msgstr "Kontakt-Tel." -#: company/models.py:105 company/templates/company/company_base.html:78 +#: company/models.py:111 company/templates/company/company_base.html:78 msgid "Email" msgstr "Email" -#: company/models.py:105 +#: company/models.py:111 msgid "Contact email address" msgstr "Kontakt-Email" -#: company/models.py:108 company/templates/company/company_base.html:85 +#: company/models.py:114 company/templates/company/company_base.html:85 msgid "Contact" msgstr "Kontakt" -#: company/models.py:109 +#: company/models.py:115 msgid "Point of contact" msgstr "Anlaufstelle" -#: company/models.py:111 +#: company/models.py:117 msgid "Link to external company information" msgstr "Link auf externe Firmeninformation" -#: company/models.py:123 +#: company/models.py:129 msgid "Do you sell items to this company?" msgstr "Verkaufen Sie Teile an diese Firma?" -#: company/models.py:125 +#: company/models.py:131 msgid "Do you purchase items from this company?" msgstr "Kaufen Sie Teile von dieser Firma?" -#: company/models.py:127 +#: company/models.py:133 msgid "Does this company manufacture parts?" msgstr "Produziert diese Firma Teile?" -#: company/models.py:283 stock/models.py:338 +#: company/models.py:137 company/templates/company/detail.html:37 +#, fuzzy +#| msgid "Edit Currency" +msgid "Currency" +msgstr "Währung bearbeiten" + +#: company/models.py:313 stock/models.py:338 #: stock/templates/stock/item_base.html:177 msgid "Base Part" msgstr "Basisteil" -#: company/models.py:288 +#: company/models.py:318 msgid "Select part" msgstr "Teil auswählen" -#: company/models.py:294 +#: company/models.py:324 msgid "Select supplier" msgstr "Zulieferer auswählen" -#: company/models.py:297 +#: company/models.py:327 msgid "Supplier stock keeping unit" msgstr "Stock Keeping Units (SKU) des Zulieferers" -#: company/models.py:304 +#: company/models.py:334 msgid "Select manufacturer" msgstr "Hersteller auswählen" -#: company/models.py:308 +#: company/models.py:338 msgid "Manufacturer part number" msgstr "Hersteller-Teilenummer" -#: company/models.py:310 +#: company/models.py:340 msgid "URL for external supplier part link" msgstr "Teil-URL des Zulieferers" -#: company/models.py:312 +#: company/models.py:342 msgid "Supplier part description" msgstr "Zuliefererbeschreibung des Teils" -#: company/models.py:316 +#: company/models.py:346 msgid "Minimum charge (e.g. stocking fee)" msgstr "Mindestpreis" -#: company/models.py:318 +#: company/models.py:348 msgid "Part packaging" msgstr "Teile-Packaging" @@ -1538,27 +1589,45 @@ msgstr "Firmendetails" msgid "Phone" msgstr "Telefon" -#: company/templates/company/detail.html:16 +#: company/templates/company/detail.html:18 +#, fuzzy +#| msgid "Company name" +msgid "Company Name" +msgstr "Firmenname" + +#: company/templates/company/detail.html:31 +#, fuzzy +#| msgid "No lines specified" +msgid "No website specified" +msgstr "Keine Zeilen angegeben" + +#: company/templates/company/detail.html:40 +#, fuzzy +#| msgid "Delete Currency" +msgid "Uses default currency" +msgstr "Währung entfernen" + +#: company/templates/company/detail.html:52 #: company/templates/company/supplier_part_base.html:84 #: company/templates/company/supplier_part_detail.html:30 part/bom.py:172 #: templates/js/company.js:44 templates/js/company.js:188 msgid "Manufacturer" msgstr "Hersteller" -#: company/templates/company/detail.html:21 +#: company/templates/company/detail.html:57 #: company/templates/company/supplier_part_base.html:74 #: company/templates/company/supplier_part_detail.html:21 #: order/templates/order/order_base.html:79 #: order/templates/order/order_wizard/select_pos.html:30 part/bom.py:170 #: stock/templates/stock/item_base.html:287 templates/js/company.js:48 -#: templates/js/company.js:164 templates/js/order.js:154 +#: templates/js/company.js:164 templates/js/order.js:155 msgid "Supplier" msgstr "Zulieferer" -#: company/templates/company/detail.html:26 +#: company/templates/company/detail.html:62 #: order/templates/order/sales_order_base.html:81 stock/models.py:373 #: stock/models.py:374 stock/templates/stock/item_base.html:204 -#: templates/js/company.js:40 templates/js/order.js:236 +#: templates/js/company.js:40 templates/js/order.js:237 msgid "Customer" msgstr "Kunde" @@ -1609,21 +1678,21 @@ msgstr "Neues Teil" msgid "Create new Part" msgstr "Neues Teil hinzufügen" -#: company/templates/company/detail_part.html:69 company/views.py:55 +#: company/templates/company/detail_part.html:69 company/views.py:56 #: part/templates/part/supplier.html:47 msgid "New Supplier" msgstr "Neuer Zulieferer" -#: company/templates/company/detail_part.html:70 company/views.py:194 +#: company/templates/company/detail_part.html:70 company/views.py:195 msgid "Create new Supplier" msgstr "Neuen Zulieferer anlegen" -#: company/templates/company/detail_part.html:75 company/views.py:62 +#: company/templates/company/detail_part.html:75 company/views.py:63 #: part/templates/part/supplier.html:53 msgid "New Manufacturer" msgstr "Neuer Hersteller" -#: company/templates/company/detail_part.html:76 company/views.py:197 +#: company/templates/company/detail_part.html:76 company/views.py:198 msgid "Create new Manufacturer" msgstr "Neuen Hersteller anlegen" @@ -1658,7 +1727,7 @@ msgstr "" #: order/templates/order/purchase_orders.html:7 #: order/templates/order/purchase_orders.html:12 #: part/templates/part/orders.html:9 part/templates/part/tabs.html:48 -#: templates/InvenTree/settings/tabs.html:28 templates/navbar.html:33 +#: templates/InvenTree/settings/tabs.html:31 templates/navbar.html:33 #: users/models.py:31 msgid "Purchase Orders" msgstr "Bestellungen" @@ -1678,7 +1747,7 @@ msgstr "Neue Bestellung" #: order/templates/order/sales_orders.html:7 #: order/templates/order/sales_orders.html:12 #: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:56 -#: templates/InvenTree/settings/tabs.html:31 templates/navbar.html:42 +#: templates/InvenTree/settings/tabs.html:34 templates/navbar.html:42 #: users/models.py:32 msgid "Sales Orders" msgstr "Bestellungen" @@ -1751,8 +1820,8 @@ msgstr "Teil bestellen" msgid "Pricing Information" msgstr "Preisinformationen ansehen" -#: company/templates/company/supplier_part_pricing.html:17 company/views.py:412 -#: part/templates/part/sale_prices.html:14 part/views.py:2350 +#: company/templates/company/supplier_part_pricing.html:17 company/views.py:459 +#: part/templates/part/sale_prices.html:14 part/views.py:2546 msgid "Add Price Break" msgstr "Preisstaffel hinzufügen" @@ -1788,7 +1857,7 @@ msgstr "Bepreisung" #: company/templates/company/supplier_part_tabs.html:8 #: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18 #: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155 -#: templates/InvenTree/settings/tabs.html:22 templates/js/part.js:192 +#: templates/InvenTree/settings/tabs.html:25 templates/js/part.js:192 #: templates/js/part.js:418 templates/js/stock.js:502 templates/navbar.html:22 #: users/models.py:29 msgid "Stock" @@ -1799,98 +1868,98 @@ msgid "Orders" msgstr "Bestellungen" #: company/templates/company/tabs.html:9 -#: order/templates/order/receive_parts.html:14 part/models.py:295 +#: order/templates/order/receive_parts.html:14 part/models.py:316 #: part/templates/part/cat_link.html:7 part/templates/part/category.html:94 #: part/templates/part/category_tabs.html:6 -#: templates/InvenTree/settings/tabs.html:19 templates/navbar.html:19 +#: templates/InvenTree/settings/tabs.html:22 templates/navbar.html:19 #: templates/stats.html:8 templates/stats.html:17 users/models.py:28 msgid "Parts" msgstr "Teile" -#: company/views.py:54 part/templates/part/tabs.html:42 +#: company/views.py:55 part/templates/part/tabs.html:42 #: templates/navbar.html:31 msgid "Suppliers" msgstr "Zulieferer" -#: company/views.py:61 templates/navbar.html:32 +#: company/views.py:62 templates/navbar.html:32 msgid "Manufacturers" msgstr "Hersteller" -#: company/views.py:68 templates/navbar.html:41 +#: company/views.py:69 templates/navbar.html:41 msgid "Customers" msgstr "Kunden" -#: company/views.py:69 +#: company/views.py:70 msgid "New Customer" msgstr "Neuer Kunde" -#: company/views.py:77 +#: company/views.py:78 msgid "Companies" msgstr "Firmen" -#: company/views.py:78 +#: company/views.py:79 msgid "New Company" msgstr "Neue Firma" -#: company/views.py:156 +#: company/views.py:157 msgid "Update Company Image" msgstr "Firmenbild aktualisieren" -#: company/views.py:162 +#: company/views.py:163 msgid "Updated company image" msgstr "Aktualisiertes Firmenbild" -#: company/views.py:172 +#: company/views.py:173 msgid "Edit Company" msgstr "Firma bearbeiten" -#: company/views.py:177 +#: company/views.py:178 msgid "Edited company information" msgstr "Firmeninformation bearbeitet" -#: company/views.py:200 +#: company/views.py:201 msgid "Create new Customer" msgstr "Neuen Kunden anlegen" -#: company/views.py:202 +#: company/views.py:203 msgid "Create new Company" msgstr "Neue Firma anlegen" -#: company/views.py:229 +#: company/views.py:230 msgid "Created new company" msgstr "Neue Firma angelegt" -#: company/views.py:239 +#: company/views.py:240 msgid "Delete Company" msgstr "Firma löschen" -#: company/views.py:245 +#: company/views.py:246 msgid "Company was deleted" msgstr "Firma gelöscht" -#: company/views.py:270 +#: company/views.py:271 msgid "Edit Supplier Part" msgstr "Zuliefererteil bearbeiten" -#: company/views.py:280 templates/js/stock.js:846 +#: company/views.py:289 templates/js/stock.js:846 msgid "Create new Supplier Part" msgstr "Neues Zuliefererteil anlegen" -#: company/views.py:341 +#: company/views.py:388 msgid "Delete Supplier Part" msgstr "Zuliefererteil entfernen" -#: company/views.py:418 part/views.py:2356 +#: company/views.py:465 part/views.py:2552 #, fuzzy #| msgid "Add Price Break" msgid "Added new price break" msgstr "Preisstaffel hinzufügen" -#: company/views.py:454 part/views.py:2400 +#: company/views.py:521 part/views.py:2596 msgid "Edit Price Break" msgstr "Preisstaffel bearbeiten" -#: company/views.py:470 part/views.py:2416 +#: company/views.py:537 part/views.py:2612 msgid "Delete Price Break" msgstr "Preisstaffel löschen" @@ -1955,118 +2024,131 @@ msgstr "Bestell-Referenz" msgid "Enter sales order number" msgstr "Auftrag stornieren" -#: order/models.py:108 +#: order/models.py:110 msgid "Order reference" msgstr "Bestell-Referenz" -#: order/models.py:110 +#: order/models.py:112 msgid "Order description" msgstr "Bestellungs-Beschreibung" -#: order/models.py:112 +#: order/models.py:114 msgid "Link to external page" msgstr "Link auf externe Seite" -#: order/models.py:122 +#: order/models.py:124 msgid "Order notes" msgstr "Bestell-Notizen" -#: order/models.py:140 order/models.py:326 +#: order/models.py:142 order/models.py:328 #, fuzzy #| msgid "Purchase Order Details" msgid "Purchase order status" msgstr "Bestelldetails" -#: order/models.py:148 +#: order/models.py:150 msgid "Company from which the items are being ordered" msgstr "" -#: order/models.py:151 +#: order/models.py:153 msgid "Supplier order reference code" msgstr "Bestellreferenz" -#: order/models.py:160 +#: order/models.py:162 msgid "Date order was issued" msgstr "" -#: order/models.py:162 +#: order/models.py:164 #, fuzzy #| msgid "Mark order as complete" msgid "Date order was completed" msgstr "Bestellung als vollständig markieren" -#: order/models.py:185 order/models.py:267 part/views.py:1479 +#: order/models.py:187 order/models.py:269 part/views.py:1496 #: stock/models.py:244 stock/models.py:811 msgid "Quantity must be greater than zero" msgstr "Anzahl muss größer Null sein" -#: order/models.py:190 +#: order/models.py:192 msgid "Part supplier must match PO supplier" msgstr "Teile-Zulieferer muss dem Zulieferer des Kaufvertrags entsprechen" -#: order/models.py:262 +#: order/models.py:264 msgid "Lines can only be received against an order marked as 'Placed'" msgstr "Nur Teile aufgegebener Bestllungen können empfangen werden" -#: order/models.py:322 +#: order/models.py:324 msgid "Company to which the items are being sold" msgstr "" -#: order/models.py:328 +#: order/models.py:330 msgid "Customer order reference code" msgstr "Bestellreferenz" -#: order/models.py:367 +#: order/models.py:369 msgid "SalesOrder cannot be shipped as it is not currently pending" msgstr "Bestellung kann nicht versendet werden weil sie nicht anhängig ist" -#: order/models.py:454 +#: order/models.py:456 msgid "Item quantity" msgstr "Anzahl" -#: order/models.py:456 +#: order/models.py:458 msgid "Line item reference" msgstr "Position - Referenz" -#: order/models.py:458 +#: order/models.py:460 msgid "Line item notes" msgstr "Position - Notizen" -#: order/models.py:484 order/templates/order/order_base.html:9 +#: order/models.py:486 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 #: stock/templates/stock/item_base.html:259 templates/js/order.js:139 msgid "Purchase Order" msgstr "Kaufvertrag" -#: order/models.py:497 +#: order/models.py:499 msgid "Supplier part" msgstr "Zulieferer-Teil" -#: order/models.py:500 +#: order/models.py:502 msgid "Number of items received" msgstr "Empfangene Objekt-Anzahl" -#: order/models.py:594 +#: order/models.py:509 stock/models.py:457 +#: stock/templates/stock/item_base.html:266 +#, fuzzy +#| msgid "Purchase Order" +msgid "Purchase Price" +msgstr "Kaufvertrag" + +#: order/models.py:510 +#, fuzzy +#| msgid "Purchase Order" +msgid "Unit purchase price" +msgstr "Kaufvertrag" + +#: order/models.py:605 msgid "Cannot allocate stock item to a line with a different part" msgstr "Kann Lagerobjekt keiner Zeile mit einem anderen Teil hinzufügen" -#: order/models.py:596 +#: order/models.py:607 msgid "Cannot allocate stock to a line without a part" msgstr "Kann Lagerobjekt keiner Zeile ohne Teil hinzufügen" -#: order/models.py:599 +#: order/models.py:610 msgid "Allocation quantity cannot exceed stock quantity" msgstr "zugewiesene Anzahl darf nicht die verfügbare Anzahl überschreiten" -#: order/models.py:609 +#: order/models.py:620 msgid "Quantity must be 1 for serialized stock item" msgstr "Anzahl muss 1 für Objekte mit Seriennummer sein" -#: order/models.py:625 +#: order/models.py:636 msgid "Select stock item to allocate" msgstr "Lagerobjekt für Zuordnung auswählen" -#: order/models.py:628 +#: order/models.py:639 msgid "Enter stock allocation quantity" msgstr "Zuordnungsanzahl eingeben" @@ -2106,7 +2188,7 @@ msgstr "Bestellreferenz" msgid "Order Status" msgstr "Bestellstatus" -#: order/templates/order/order_base.html:85 templates/js/order.js:161 +#: order/templates/order/order_base.html:85 templates/js/order.js:162 msgid "Supplier Reference" msgstr "Zuliefererreferenz" @@ -2115,7 +2197,7 @@ msgid "Issued" msgstr "Aufgegeben" #: order/templates/order/order_base.html:111 -#: order/templates/order/purchase_order_detail.html:183 +#: order/templates/order/purchase_order_detail.html:193 #: order/templates/order/receive_parts.html:22 #: order/templates/order/sales_order_base.html:113 msgid "Received" @@ -2163,7 +2245,7 @@ msgid "Select existing purchase orders, or create new orders." msgstr "Bestellungen auswählen oder anlegen." #: order/templates/order/order_wizard/select_pos.html:31 -#: templates/js/order.js:185 templates/js/order.js:272 +#: templates/js/order.js:186 templates/js/order.js:273 msgid "Items" msgstr "Positionen" @@ -2219,21 +2301,27 @@ msgstr "Neuen Lagerort anlegen" msgid "No line items found" msgstr "Keine Positionen gefunden" -#: order/templates/order/purchase_order_detail.html:165 +#: order/templates/order/purchase_order_detail.html:166 #: order/templates/order/receive_parts.html:20 msgid "Order Code" msgstr "Bestellnummer" -#: order/templates/order/purchase_order_detail.html:214 +#: order/templates/order/purchase_order_detail.html:184 +#, fuzzy +#| msgid "Price" +msgid "Unit Price" +msgstr "Preis" + +#: order/templates/order/purchase_order_detail.html:225 #: order/templates/order/sales_order_detail.html:285 msgid "Edit line item" msgstr "Position bearbeiten" -#: order/templates/order/purchase_order_detail.html:215 +#: order/templates/order/purchase_order_detail.html:226 msgid "Delete line item" msgstr "Position löschen" -#: order/templates/order/purchase_order_detail.html:220 +#: order/templates/order/purchase_order_detail.html:231 msgid "Receive line item" msgstr "Position empfangen" @@ -2272,7 +2360,7 @@ msgstr "Packliste" msgid "Sales Order Details" msgstr "Auftragsdetails" -#: order/templates/order/sales_order_base.html:87 templates/js/order.js:243 +#: order/templates/order/sales_order_base.html:87 templates/js/order.js:244 msgid "Customer Reference" msgstr "Kundenreferenz" @@ -2537,103 +2625,103 @@ msgstr "Fehler beim Lesen der Stückliste (ungültige Daten)" msgid "Error reading BOM file (incorrect row size)" msgstr "Fehler beim Lesen der Stückliste (ungültige Zeilengröße)" -#: part/forms.py:60 stock/forms.py:255 +#: part/forms.py:61 stock/forms.py:255 msgid "File Format" msgstr "Dateiformat" -#: part/forms.py:60 stock/forms.py:255 +#: part/forms.py:61 stock/forms.py:255 msgid "Select output file format" msgstr "Ausgabe-Dateiformat auswählen" -#: part/forms.py:62 +#: part/forms.py:63 msgid "Cascading" msgstr "Kaskadierend" -#: part/forms.py:62 +#: part/forms.py:63 msgid "Download cascading / multi-level BOM" msgstr "Kaskadierende Stückliste herunterladen" -#: part/forms.py:64 +#: part/forms.py:65 msgid "Levels" msgstr "" -#: part/forms.py:64 +#: part/forms.py:65 msgid "Select maximum number of BOM levels to export (0 = all levels)" msgstr "" -#: part/forms.py:66 +#: part/forms.py:67 #, fuzzy #| msgid "New Parameter" msgid "Include Parameter Data" msgstr "Neuer Parameter" -#: part/forms.py:66 +#: part/forms.py:67 msgid "Include part parameters data in exported BOM" msgstr "" -#: part/forms.py:68 +#: part/forms.py:69 #, fuzzy #| msgid "Include stock in sublocations" msgid "Include Stock Data" msgstr "Bestand in Unterlagerorten einschließen" -#: part/forms.py:68 +#: part/forms.py:69 #, fuzzy #| msgid "Include parts in subcategories" msgid "Include part stock data in exported BOM" msgstr "Teile in Unterkategorien einschließen" -#: part/forms.py:70 +#: part/forms.py:71 #, fuzzy #| msgid "New Supplier Part" msgid "Include Supplier Data" msgstr "Neues Zulieferer-Teil" -#: part/forms.py:70 +#: part/forms.py:71 msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:91 part/models.py:1644 +#: part/forms.py:92 part/models.py:1715 msgid "Parent Part" msgstr "Ausgangsteil" -#: part/forms.py:92 part/templates/part/bom_duplicate.html:7 +#: part/forms.py:93 part/templates/part/bom_duplicate.html:7 #, fuzzy #| msgid "Select parent part" msgid "Select parent part to copy BOM from" msgstr "Ausgangsteil auswählen" -#: part/forms.py:98 +#: part/forms.py:99 #, fuzzy #| msgid "Select from existing images" msgid "Clear existing BOM items" msgstr "Aus vorhandenen Bildern auswählen" -#: part/forms.py:103 +#: part/forms.py:104 #, fuzzy #| msgid "Confim BOM item deletion" msgid "Confirm BOM duplication" msgstr "Löschung von BOM-Position bestätigen" -#: part/forms.py:121 +#: part/forms.py:122 msgid "Confirm that the BOM is correct" msgstr "Bestätigen, dass die Stückliste korrekt ist" -#: part/forms.py:133 +#: part/forms.py:134 msgid "Select BOM file to upload" msgstr "Stücklisten-Datei zum Upload auswählen" -#: part/forms.py:152 +#: part/forms.py:153 #, fuzzy #| msgid "Delete Parts" msgid "Related Part" msgstr "Teile löschen" -#: part/forms.py:171 +#: part/forms.py:172 msgid "Select part category" msgstr "Teilekategorie wählen" -#: part/forms.py:187 +#: part/forms.py:188 #, fuzzy #| msgid "Perform 'deep copy' which will duplicate all BOM data for this part" msgid "Duplicate all BOM data for this part" @@ -2641,181 +2729,206 @@ msgstr "" "Tiefe Kopie ausführen. Dies wird alle Daten der Stückliste für dieses Teil " "duplizieren" -#: part/forms.py:188 +#: part/forms.py:189 msgid "Copy BOM" msgstr "" -#: part/forms.py:193 +#: part/forms.py:194 msgid "Duplicate all parameter data for this part" msgstr "" -#: part/forms.py:194 +#: part/forms.py:195 #, fuzzy #| msgid "Parameters" msgid "Copy Parameters" msgstr "Parameter" -#: part/forms.py:199 +#: part/forms.py:200 msgid "Confirm part creation" msgstr "Erstellen des Teils bestätigen" -#: part/forms.py:296 +#: part/forms.py:205 +#, fuzzy +#| msgid "No part parameter templates found" +msgid "Include category parameter templates" +msgstr "Keine Teilparametervorlagen gefunden" + +#: part/forms.py:210 +#, fuzzy +#| msgid "No part parameter templates found" +msgid "Include parent categories parameter templates" +msgstr "Keine Teilparametervorlagen gefunden" + +#: part/forms.py:285 +#, fuzzy +#| msgid "Parameter template name must be unique" +msgid "Add parameter template to same level categories" +msgstr "Vorlagen-Name des Parameters muss eindeutig sein" + +#: part/forms.py:289 +#, fuzzy +#| msgid "Parameter template name must be unique" +msgid "Add parameter template to all categories" +msgstr "Vorlagen-Name des Parameters muss eindeutig sein" + +#: part/forms.py:331 msgid "Input quantity for price calculation" msgstr "Eintragsmenge zur Preisberechnung" -#: part/models.py:67 +#: part/models.py:68 msgid "Default location for parts in this category" msgstr "Standard-Standort für Teile dieser Kategorie" -#: part/models.py:70 +#: part/models.py:71 msgid "Default keywords for parts in this category" msgstr "Standard-Stichworte für Teile dieser Kategorie" -#: part/models.py:76 part/templates/part/part_app_base.html:9 +#: part/models.py:77 part/models.py:1760 +#: part/templates/part/part_app_base.html:9 msgid "Part Category" msgstr "Teilkategorie" -#: part/models.py:77 part/templates/part/category.html:18 +#: part/models.py:78 part/templates/part/category.html:18 #: part/templates/part/category.html:89 templates/stats.html:12 msgid "Part Categories" msgstr "Teile-Kategorien" -#: part/models.py:346 part/models.py:356 +#: part/models.py:408 part/models.py:418 #, python-brace-format msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" msgstr "Teil '{p1}' wird in Stückliste für Teil '{p2}' benutzt (rekursiv)" -#: part/models.py:453 +#: part/models.py:515 #, fuzzy #| msgid "No serial numbers found" msgid "Next available serial numbers are" msgstr "Keine Seriennummern gefunden" -#: part/models.py:457 +#: part/models.py:519 msgid "Next available serial number is" msgstr "" -#: part/models.py:462 +#: part/models.py:524 #, fuzzy #| msgid "Empty serial number string" msgid "Most recent serial number is" msgstr "Keine Seriennummer angegeben" -#: part/models.py:541 +#: part/models.py:603 msgid "Duplicate IPN not allowed in part settings" msgstr "" -#: part/models.py:552 +#: part/models.py:614 msgid "Part must be unique for name, IPN and revision" msgstr "Namen, Teile- und Revisionsnummern müssen eindeutig sein" -#: part/models.py:581 part/templates/part/detail.html:19 +#: part/models.py:644 part/templates/part/detail.html:19 msgid "Part name" msgstr "Name des Teils" -#: part/models.py:585 +#: part/models.py:648 msgid "Is this part a template part?" msgstr "Ist dieses Teil eine Vorlage?" -#: part/models.py:594 +#: part/models.py:657 msgid "Is this part a variant of another part?" msgstr "Ist dieses Teil eine Variante eines anderen Teils?" -#: part/models.py:596 +#: part/models.py:659 msgid "Part description" msgstr "Beschreibung des Teils" -#: part/models.py:598 +#: part/models.py:661 msgid "Part keywords to improve visibility in search results" msgstr "Schlüsselworte um die Sichtbarkeit in Suchergebnissen zu verbessern" -#: part/models.py:603 +#: part/models.py:666 msgid "Part category" msgstr "Teile-Kategorie" -#: part/models.py:605 +#: part/models.py:668 msgid "Internal Part Number" msgstr "Interne Teilenummer" -#: part/models.py:607 +#: part/models.py:670 msgid "Part revision or version number" msgstr "Revisions- oder Versionsnummer" -#: part/models.py:621 +#: part/models.py:684 msgid "Where is this item normally stored?" msgstr "Wo wird dieses Teil normalerweise gelagert?" -#: part/models.py:665 +#: part/models.py:728 msgid "Default supplier part" msgstr "Standard-Zulieferer?" -#: part/models.py:668 +#: part/models.py:731 msgid "Minimum allowed stock level" msgstr "Minimal zulässiger Lagerbestand" -#: part/models.py:670 +#: part/models.py:733 msgid "Stock keeping units for this part" msgstr "Stock Keeping Units (SKU) für dieses Teil" -#: part/models.py:674 part/templates/part/detail.html:158 +#: part/models.py:737 part/templates/part/detail.html:158 #: templates/js/table_filters.js:260 msgid "Assembly" msgstr "Baugruppe" -#: part/models.py:675 +#: part/models.py:738 msgid "Can this part be built from other parts?" msgstr "Kann dieses Teil aus anderen Teilen angefertigt werden?" -#: part/models.py:681 +#: part/models.py:744 msgid "Can this part be used to build other parts?" msgstr "Kann dieses Teil zum Bau von anderen genutzt werden?" -#: part/models.py:687 +#: part/models.py:750 msgid "Does this part have tracking for unique items?" msgstr "Hat dieses Teil Tracking für einzelne Objekte?" -#: part/models.py:692 +#: part/models.py:755 msgid "Can this part be purchased from external suppliers?" msgstr "Kann dieses Teil von externen Zulieferern gekauft werden?" -#: part/models.py:697 +#: part/models.py:760 msgid "Can this part be sold to customers?" msgstr "Kann dieses Teil an Kunden verkauft werden?" -#: part/models.py:701 part/templates/part/detail.html:215 +#: part/models.py:764 part/templates/part/detail.html:215 #: templates/js/table_filters.js:19 templates/js/table_filters.js:55 #: templates/js/table_filters.js:186 templates/js/table_filters.js:243 msgid "Active" msgstr "Aktiv" -#: part/models.py:702 +#: part/models.py:765 msgid "Is this part active?" msgstr "Ist dieses Teil aktiv?" -#: part/models.py:706 part/templates/part/detail.html:138 +#: part/models.py:769 part/templates/part/detail.html:138 #: templates/js/table_filters.js:27 msgid "Virtual" msgstr "Virtuell" -#: part/models.py:707 +#: part/models.py:770 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:709 +#: part/models.py:772 msgid "Part notes - supports Markdown formatting" msgstr "Bemerkungen - unterstüzt Markdown-Formatierung" -#: part/models.py:711 +#: part/models.py:774 msgid "Stored BOM checksum" msgstr "Prüfsumme der Stückliste gespeichert" -#: part/models.py:1517 +#: part/models.py:1588 #, fuzzy #| msgid "Stock item cannot be created for a template Part" msgid "Test templates can only be created for trackable parts" msgstr "Lagerobjekt kann nicht für Vorlagen-Teile angelegt werden" -#: part/models.py:1534 +#: part/models.py:1605 #, fuzzy #| msgid "" #| "A stock item with this serial number already exists for template part " @@ -2825,133 +2938,140 @@ msgstr "" "Ein Teil mit dieser Seriennummer existiert bereits für die Teilevorlage " "{part}" -#: part/models.py:1553 templates/js/part.js:567 templates/js/stock.js:92 +#: part/models.py:1624 templates/js/part.js:567 templates/js/stock.js:92 #, fuzzy #| msgid "Instance Name" msgid "Test Name" msgstr "Instanzname" -#: part/models.py:1554 +#: part/models.py:1625 #, fuzzy #| msgid "Serial number for this item" msgid "Enter a name for the test" msgstr "Seriennummer für dieses Teil" -#: part/models.py:1559 +#: part/models.py:1630 #, fuzzy #| msgid "Description" msgid "Test Description" msgstr "Beschreibung" -#: part/models.py:1560 +#: part/models.py:1631 #, fuzzy #| msgid "Brief description of the build" msgid "Enter description for this test" msgstr "Kurze Beschreibung des Baus" -#: part/models.py:1565 templates/js/part.js:576 +#: part/models.py:1636 templates/js/part.js:576 #: templates/js/table_filters.js:172 msgid "Required" msgstr "benötigt" -#: part/models.py:1566 +#: part/models.py:1637 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1571 templates/js/part.js:584 +#: part/models.py:1642 templates/js/part.js:584 #, fuzzy #| msgid "Required Parts" msgid "Requires Value" msgstr "benötigte Teile" -#: part/models.py:1572 +#: part/models.py:1643 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1577 templates/js/part.js:591 +#: part/models.py:1648 templates/js/part.js:591 #, fuzzy #| msgid "Delete Attachment" msgid "Requires Attachment" msgstr "Anhang löschen" -#: part/models.py:1578 +#: part/models.py:1649 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1611 +#: part/models.py:1682 msgid "Parameter template name must be unique" msgstr "Vorlagen-Name des Parameters muss eindeutig sein" -#: part/models.py:1616 +#: part/models.py:1687 msgid "Parameter Name" msgstr "Name des Parameters" -#: part/models.py:1618 +#: part/models.py:1689 msgid "Parameter Units" msgstr "Parameter Einheit" -#: part/models.py:1646 +#: part/models.py:1717 part/models.py:1765 +#: templates/InvenTree/settings/category.html:62 msgid "Parameter Template" msgstr "Parameter Vorlage" -#: part/models.py:1648 +#: part/models.py:1719 msgid "Parameter Value" msgstr "Parameter Wert" -#: part/models.py:1685 +#: part/models.py:1769 +#, fuzzy +#| msgid "Parameter Value" +msgid "Default Parameter Value" +msgstr "Parameter Wert" + +#: part/models.py:1799 msgid "Select parent part" msgstr "Ausgangsteil auswählen" -#: part/models.py:1693 +#: part/models.py:1807 msgid "Select part to be used in BOM" msgstr "Teil für die Nutzung in der Stückliste auswählen" -#: part/models.py:1699 +#: part/models.py:1813 msgid "BOM quantity for this BOM item" msgstr "Stücklisten-Anzahl für dieses Stücklisten-Teil" -#: part/models.py:1701 +#: part/models.py:1815 #, fuzzy #| msgid "Confim BOM item deletion" msgid "This BOM item is optional" msgstr "Löschung von BOM-Position bestätigen" -#: part/models.py:1704 +#: part/models.py:1818 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "Geschätzter Ausschuss (absolut oder prozentual)" -#: part/models.py:1707 +#: part/models.py:1821 msgid "BOM item reference" msgstr "Referenz des Objekts auf der Stückliste" -#: part/models.py:1710 +#: part/models.py:1824 msgid "BOM item notes" msgstr "Notizen zum Stücklisten-Objekt" -#: part/models.py:1712 +#: part/models.py:1826 msgid "BOM line checksum" msgstr "Prüfsumme der Stückliste" -#: part/models.py:1779 part/views.py:1485 part/views.py:1537 +#: part/models.py:1893 part/views.py:1502 part/views.py:1554 #: stock/models.py:234 #, fuzzy #| msgid "Overage must be an integer value or a percentage" msgid "Quantity must be integer value for trackable parts" msgstr "Überschuss muss eine Ganzzahl oder ein Prozentwert sein" -#: part/models.py:1795 +#: part/models.py:1909 #, fuzzy #| msgid "New BOM Item" msgid "BOM Item" msgstr "Neue Stücklistenposition" -#: part/models.py:1910 +#: part/models.py:2024 #, fuzzy #| msgid "Select a part" msgid "Select Related Part" msgstr "Teil auswählen" -#: part/models.py:1942 +#: part/models.py:2056 msgid "" "Error creating relationship: check that the part is not related to itself " "and that the relationship is unique" @@ -3047,7 +3167,7 @@ msgstr "Stückliste validieren" msgid "Validate" msgstr "BOM validieren" -#: part/templates/part/bom.html:62 part/views.py:1776 +#: part/templates/part/bom.html:62 part/views.py:1793 msgid "Export Bill of Materials" msgstr "Stückliste exportieren" @@ -3169,7 +3289,7 @@ msgstr "Neuen Bau beginnen" msgid "All parts" msgstr "Alle Teile" -#: part/templates/part/category.html:24 part/views.py:2167 +#: part/templates/part/category.html:24 part/views.py:2184 msgid "Create new part category" msgstr "Teilkategorie anlegen" @@ -3259,7 +3379,7 @@ msgstr "Teilkategorie anlegen" msgid "Create new Part Category" msgstr "Teilkategorie anlegen" -#: part/templates/part/category.html:216 stock/views.py:1338 +#: part/templates/part/category.html:216 stock/views.py:1342 msgid "Create new Stock Location" msgstr "Neuen Lager-Standort erstellen" @@ -3331,7 +3451,7 @@ msgstr "Einheiten" msgid "Minimum Stock" msgstr "Minimaler Lagerbestand" -#: part/templates/part/detail.html:114 templates/js/order.js:262 +#: part/templates/part/detail.html:114 templates/js/order.js:263 msgid "Creation Date" msgstr "Erstelldatum" @@ -3428,7 +3548,9 @@ msgstr "Teilparameter" msgid "Add new parameter" msgstr "Parameter hinzufügen" -#: part/templates/part/params.html:15 templates/InvenTree/settings/part.html:37 +#: part/templates/part/params.html:15 +#: templates/InvenTree/settings/category.html:29 +#: templates/InvenTree/settings/part.html:38 msgid "New Parameter" msgstr "Neuer Parameter" @@ -3438,7 +3560,7 @@ msgid "Value" msgstr "Wert" #: part/templates/part/params.html:41 part/templates/part/related.html:41 -#: part/templates/part/supplier.html:19 users/models.py:148 +#: part/templates/part/supplier.html:19 users/models.py:152 msgid "Delete" msgstr "Löschen" @@ -3688,230 +3810,248 @@ msgstr "Neues Teil hinzufügen" msgid "New Variant" msgstr "Varianten" -#: part/views.py:82 +#: part/views.py:84 #, fuzzy #| msgid "Allocated Parts" msgid "Add Related Part" msgstr "Zugeordnete Teile" -#: part/views.py:138 +#: part/views.py:140 #, fuzzy #| msgid "Delete Supplier Part" msgid "Delete Related Part" msgstr "Zuliefererteil entfernen" -#: part/views.py:150 +#: part/views.py:152 msgid "Add part attachment" msgstr "Teilanhang hinzufügen" -#: part/views.py:205 templates/attachment_table.html:34 +#: part/views.py:207 templates/attachment_table.html:34 msgid "Edit attachment" msgstr "Anhang bearbeiten" -#: part/views.py:211 +#: part/views.py:213 msgid "Part attachment updated" msgstr "Teilanhang aktualisiert" -#: part/views.py:226 +#: part/views.py:228 msgid "Delete Part Attachment" msgstr "Teilanhang löschen" -#: part/views.py:234 +#: part/views.py:236 msgid "Deleted part attachment" msgstr "Teilanhang gelöscht" -#: part/views.py:243 +#: part/views.py:245 #, fuzzy #| msgid "Create Part Parameter Template" msgid "Create Test Template" msgstr "Teilparametervorlage anlegen" -#: part/views.py:272 +#: part/views.py:274 #, fuzzy #| msgid "Edit Template" msgid "Edit Test Template" msgstr "Vorlage bearbeiten" -#: part/views.py:288 +#: part/views.py:290 #, fuzzy #| msgid "Delete Template" msgid "Delete Test Template" msgstr "Vorlage löschen" -#: part/views.py:297 +#: part/views.py:299 msgid "Set Part Category" msgstr "Teilkategorie auswählen" -#: part/views.py:347 +#: part/views.py:349 #, python-brace-format msgid "Set category for {n} parts" msgstr "Kategorie für {n} Teile setzen" -#: part/views.py:382 +#: part/views.py:384 msgid "Create Variant" msgstr "Variante anlegen" -#: part/views.py:464 +#: part/views.py:466 msgid "Duplicate Part" msgstr "Teil duplizieren" -#: part/views.py:471 +#: part/views.py:473 msgid "Copied part" msgstr "Teil kopiert" -#: part/views.py:525 part/views.py:655 +#: part/views.py:527 part/views.py:661 msgid "Possible matches exist - confirm creation of new part" msgstr "" -#: part/views.py:590 templates/js/stock.js:840 +#: part/views.py:592 templates/js/stock.js:840 msgid "Create New Part" msgstr "Neues Teil anlegen" -#: part/views.py:597 +#: part/views.py:599 msgid "Created new part" msgstr "Neues Teil angelegt" -#: part/views.py:813 +#: part/views.py:830 msgid "Part QR Code" msgstr "Teil-QR-Code" -#: part/views.py:832 +#: part/views.py:849 msgid "Upload Part Image" msgstr "Teilbild hochladen" -#: part/views.py:840 part/views.py:877 +#: part/views.py:857 part/views.py:894 msgid "Updated part image" msgstr "Teilbild aktualisiert" -#: part/views.py:849 +#: part/views.py:866 msgid "Select Part Image" msgstr "Teilbild auswählen" -#: part/views.py:880 +#: part/views.py:897 msgid "Part image not found" msgstr "Teilbild nicht gefunden" -#: part/views.py:891 +#: part/views.py:908 msgid "Edit Part Properties" msgstr "Teileigenschaften bearbeiten" -#: part/views.py:918 +#: part/views.py:935 #, fuzzy #| msgid "Duplicate Part" msgid "Duplicate BOM" msgstr "Teil duplizieren" -#: part/views.py:949 +#: part/views.py:966 #, fuzzy #| msgid "Confirm unallocation of build stock" msgid "Confirm duplication of BOM from parent" msgstr "Zuweisungsaufhebung bestätigen" -#: part/views.py:970 +#: part/views.py:987 msgid "Validate BOM" msgstr "BOM validieren" -#: part/views.py:993 +#: part/views.py:1010 #, fuzzy #| msgid "Confirm that the BOM is correct" msgid "Confirm that the BOM is valid" msgstr "Bestätigen, dass die Stückliste korrekt ist" -#: part/views.py:1004 +#: part/views.py:1021 #, fuzzy #| msgid "Validate Bill of Materials" msgid "Validated Bill of Materials" msgstr "Stückliste validieren" -#: part/views.py:1138 +#: part/views.py:1155 msgid "No BOM file provided" msgstr "Keine Stückliste angegeben" -#: part/views.py:1488 +#: part/views.py:1505 msgid "Enter a valid quantity" msgstr "Bitte eine gültige Anzahl eingeben" -#: part/views.py:1513 part/views.py:1516 +#: part/views.py:1530 part/views.py:1533 msgid "Select valid part" msgstr "Bitte ein gültiges Teil auswählen" -#: part/views.py:1522 +#: part/views.py:1539 msgid "Duplicate part selected" msgstr "Teil doppelt ausgewählt" -#: part/views.py:1560 +#: part/views.py:1577 msgid "Select a part" msgstr "Teil auswählen" -#: part/views.py:1566 +#: part/views.py:1583 #, fuzzy #| msgid "Select part to be used in BOM" msgid "Selected part creates a circular BOM" msgstr "Teil für die Nutzung in der Stückliste auswählen" -#: part/views.py:1570 +#: part/views.py:1587 msgid "Specify quantity" msgstr "Anzahl angeben" -#: part/views.py:1826 +#: part/views.py:1843 msgid "Confirm Part Deletion" msgstr "Löschen des Teils bestätigen" -#: part/views.py:1835 +#: part/views.py:1852 msgid "Part was deleted" msgstr "Teil wurde gelöscht" -#: part/views.py:1844 +#: part/views.py:1861 msgid "Part Pricing" msgstr "Teilbepreisung" -#: part/views.py:1958 +#: part/views.py:1975 msgid "Create Part Parameter Template" msgstr "Teilparametervorlage anlegen" -#: part/views.py:1968 +#: part/views.py:1985 msgid "Edit Part Parameter Template" msgstr "Teilparametervorlage bearbeiten" -#: part/views.py:1977 +#: part/views.py:1994 msgid "Delete Part Parameter Template" msgstr "Teilparametervorlage löschen" -#: part/views.py:1987 +#: part/views.py:2004 msgid "Create Part Parameter" msgstr "Teilparameter anlegen" -#: part/views.py:2039 +#: part/views.py:2056 msgid "Edit Part Parameter" msgstr "Teilparameter bearbeiten" -#: part/views.py:2055 +#: part/views.py:2072 msgid "Delete Part Parameter" msgstr "Teilparameter löschen" -#: part/views.py:2114 +#: part/views.py:2131 msgid "Edit Part Category" msgstr "Teilkategorie bearbeiten" -#: part/views.py:2151 +#: part/views.py:2168 msgid "Delete Part Category" msgstr "Teilkategorie löschen" -#: part/views.py:2159 +#: part/views.py:2176 msgid "Part category was deleted" msgstr "Teilekategorie wurde gelöscht" -#: part/views.py:2222 +#: part/views.py:2232 +#, fuzzy +#| msgid "Create Part Parameter Template" +msgid "Create Category Parameter Template" +msgstr "Teilparametervorlage anlegen" + +#: part/views.py:2335 +#, fuzzy +#| msgid "Edit Part Parameter Template" +msgid "Edit Category Parameter Template" +msgstr "Teilparametervorlage bearbeiten" + +#: part/views.py:2393 +#, fuzzy +#| msgid "Delete Part Parameter Template" +msgid "Delete Category Parameter Template" +msgstr "Teilparametervorlage löschen" + +#: part/views.py:2418 #, fuzzy #| msgid "Create BOM item" msgid "Create BOM Item" msgstr "BOM-Position anlegen" -#: part/views.py:2290 +#: part/views.py:2486 msgid "Edit BOM item" msgstr "BOM-Position beaarbeiten" -#: part/views.py:2340 +#: part/views.py:2536 msgid "Confim BOM item deletion" msgstr "Löschung von BOM-Position bestätigen" @@ -4019,7 +4159,7 @@ msgstr "Ziel-Lagerbestand" msgid "Add note (required)" msgstr "" -#: stock/forms.py:371 stock/views.py:916 stock/views.py:1114 +#: stock/forms.py:371 stock/views.py:920 stock/views.py:1118 msgid "Confirm stock adjustment" msgstr "Bestands-Anpassung bestätigen" @@ -4144,12 +4284,6 @@ msgstr "Objekt löschen wenn Lagerbestand aufgebraucht" msgid "Stock Item Notes" msgstr "Lagerobjekt-Notizen" -#: stock/models.py:457 stock/templates/stock/item_base.html:266 -#, fuzzy -#| msgid "Purchase Order" -msgid "Purchase Price" -msgstr "Kaufvertrag" - #: stock/models.py:458 msgid "Single unit purchase price at time of purchase" msgstr "" @@ -4635,7 +4769,7 @@ msgstr "Sind Sie sicher, dass Sie diesen Anhang löschen wollen?" msgid "The following stock items will be uninstalled" msgstr "Die folgenden Objekte werden erstellt" -#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1310 +#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1314 #, fuzzy #| msgid "Count Stock Items" msgid "Convert Stock Item" @@ -4673,260 +4807,260 @@ msgstr "Kinder" msgid "Installed Items" msgstr "Installiert in" -#: stock/views.py:119 +#: stock/views.py:123 msgid "Edit Stock Location" msgstr "Lagerobjekt-Standort bearbeiten" -#: stock/views.py:144 +#: stock/views.py:148 msgid "Stock Location QR code" msgstr "QR-Code für diesen Standort" -#: stock/views.py:163 +#: stock/views.py:167 #, fuzzy #| msgid "Add Attachment" msgid "Add Stock Item Attachment" msgstr "Anhang hinzufügen" -#: stock/views.py:210 +#: stock/views.py:214 #, fuzzy #| msgid "Edit Stock Item" msgid "Edit Stock Item Attachment" msgstr "Lagerobjekt bearbeiten" -#: stock/views.py:227 +#: stock/views.py:231 #, fuzzy #| msgid "Delete Part Attachment" msgid "Delete Stock Item Attachment" msgstr "Teilanhang löschen" -#: stock/views.py:244 +#: stock/views.py:248 #, fuzzy #| msgid "Item assigned to customer?" msgid "Assign to Customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: stock/views.py:254 +#: stock/views.py:258 msgid "Customer must be specified" msgstr "" -#: stock/views.py:278 +#: stock/views.py:282 #, fuzzy #| msgid "Part Stock" msgid "Return to Stock" msgstr "Teilbestand" -#: stock/views.py:288 +#: stock/views.py:292 #, fuzzy #| msgid "Include sublocations" msgid "Specify a valid location" msgstr "Unterlagerorte einschließen" -#: stock/views.py:299 +#: stock/views.py:303 msgid "Stock item returned from customer" msgstr "" -#: stock/views.py:309 +#: stock/views.py:313 #, fuzzy #| msgid "Select valid part" msgid "Select Label Template" msgstr "Bitte ein gültiges Teil auswählen" -#: stock/views.py:332 +#: stock/views.py:336 #, fuzzy #| msgid "Select valid part" msgid "Select valid label" msgstr "Bitte ein gültiges Teil auswählen" -#: stock/views.py:396 +#: stock/views.py:400 #, fuzzy #| msgid "Delete Template" msgid "Delete All Test Data" msgstr "Vorlage löschen" -#: stock/views.py:412 +#: stock/views.py:416 #, fuzzy #| msgid "Confirm Part Deletion" msgid "Confirm test data deletion" msgstr "Löschen des Teils bestätigen" -#: stock/views.py:432 +#: stock/views.py:436 msgid "Add Test Result" msgstr "" -#: stock/views.py:473 +#: stock/views.py:477 #, fuzzy #| msgid "Edit Template" msgid "Edit Test Result" msgstr "Vorlage bearbeiten" -#: stock/views.py:491 +#: stock/views.py:495 #, fuzzy #| msgid "Delete Template" msgid "Delete Test Result" msgstr "Vorlage löschen" -#: stock/views.py:503 +#: stock/views.py:507 #, fuzzy #| msgid "Delete Template" msgid "Select Test Report Template" msgstr "Vorlage löschen" -#: stock/views.py:518 +#: stock/views.py:522 #, fuzzy #| msgid "Select valid part" msgid "Select valid template" msgstr "Bitte ein gültiges Teil auswählen" -#: stock/views.py:571 +#: stock/views.py:575 msgid "Stock Export Options" msgstr "Lagerbestandsexportoptionen" -#: stock/views.py:693 +#: stock/views.py:697 msgid "Stock Item QR Code" msgstr "Lagerobjekt-QR-Code" -#: stock/views.py:719 +#: stock/views.py:723 #, fuzzy #| msgid "Installed in Stock Item" msgid "Install Stock Item" msgstr "In Lagerobjekt installiert" -#: stock/views.py:819 +#: stock/views.py:823 #, fuzzy #| msgid "Installed in Stock Item" msgid "Uninstall Stock Items" msgstr "In Lagerobjekt installiert" -#: stock/views.py:927 +#: stock/views.py:931 #, fuzzy #| msgid "Installed in Stock Item" msgid "Uninstalled stock items" msgstr "In Lagerobjekt installiert" -#: stock/views.py:952 +#: stock/views.py:956 msgid "Adjust Stock" msgstr "Lagerbestand anpassen" -#: stock/views.py:1062 +#: stock/views.py:1066 msgid "Move Stock Items" msgstr "Lagerobjekte bewegen" -#: stock/views.py:1063 +#: stock/views.py:1067 msgid "Count Stock Items" msgstr "Lagerobjekte zählen" -#: stock/views.py:1064 +#: stock/views.py:1068 msgid "Remove From Stock" msgstr "Aus Lagerbestand entfernen" -#: stock/views.py:1065 +#: stock/views.py:1069 msgid "Add Stock Items" msgstr "Lagerobjekte hinzufügen" -#: stock/views.py:1066 +#: stock/views.py:1070 msgid "Delete Stock Items" msgstr "Lagerobjekte löschen" -#: stock/views.py:1094 +#: stock/views.py:1098 msgid "Must enter integer value" msgstr "Nur Ganzzahl eingeben" -#: stock/views.py:1099 +#: stock/views.py:1103 msgid "Quantity must be positive" msgstr "Anzahl muss positiv sein" -#: stock/views.py:1106 +#: stock/views.py:1110 #, python-brace-format msgid "Quantity must not exceed {x}" msgstr "Anzahl darf {x} nicht überschreiten" -#: stock/views.py:1185 +#: stock/views.py:1189 #, python-brace-format msgid "Added stock to {n} items" msgstr "Vorrat zu {n} Lagerobjekten hinzugefügt" -#: stock/views.py:1200 +#: stock/views.py:1204 #, python-brace-format msgid "Removed stock from {n} items" msgstr "Vorrat von {n} Lagerobjekten entfernt" -#: stock/views.py:1213 +#: stock/views.py:1217 #, python-brace-format msgid "Counted stock for {n} items" msgstr "Bestand für {n} Objekte erfasst" -#: stock/views.py:1241 +#: stock/views.py:1245 msgid "No items were moved" msgstr "Keine Lagerobjekte wurden bewegt" -#: stock/views.py:1244 +#: stock/views.py:1248 #, python-brace-format msgid "Moved {n} items to {dest}" msgstr "{n} Teile nach {dest} bewegt" -#: stock/views.py:1263 +#: stock/views.py:1267 #, python-brace-format msgid "Deleted {n} stock items" msgstr "{n} Teile im Lager gelöscht" -#: stock/views.py:1275 +#: stock/views.py:1279 msgid "Edit Stock Item" msgstr "Lagerobjekt bearbeiten" -#: stock/views.py:1360 +#: stock/views.py:1364 msgid "Serialize Stock" msgstr "Lagerbestand erfassen" -#: stock/views.py:1454 templates/js/build.js:210 +#: stock/views.py:1458 templates/js/build.js:210 msgid "Create new Stock Item" msgstr "Neues Lagerobjekt hinzufügen" -#: stock/views.py:1555 +#: stock/views.py:1559 #, fuzzy #| msgid "Count stock items" msgid "Duplicate Stock Item" msgstr "Lagerobjekte zählen" -#: stock/views.py:1621 +#: stock/views.py:1634 msgid "Invalid quantity" msgstr "Ungültige Menge" -#: stock/views.py:1624 +#: stock/views.py:1637 #, fuzzy #| msgid "Quantity must be greater than zero" msgid "Quantity cannot be less than zero" msgstr "Anzahl muss größer Null sein" -#: stock/views.py:1628 +#: stock/views.py:1641 msgid "Invalid part selection" msgstr "Ungültige Teileauswahl" -#: stock/views.py:1676 +#: stock/views.py:1689 #, python-brace-format msgid "Created {n} new stock items" msgstr "{n} neue Lagerobjekte erstellt" -#: stock/views.py:1695 stock/views.py:1711 +#: stock/views.py:1708 stock/views.py:1724 msgid "Created new stock item" msgstr "Neues Lagerobjekt erstellt" -#: stock/views.py:1730 +#: stock/views.py:1743 msgid "Delete Stock Location" msgstr "Standort löschen" -#: stock/views.py:1744 +#: stock/views.py:1757 msgid "Delete Stock Item" msgstr "Lagerobjekt löschen" -#: stock/views.py:1756 +#: stock/views.py:1769 msgid "Delete Stock Tracking Entry" msgstr "Lagerbestands-Tracking-Eintrag löschen" -#: stock/views.py:1775 +#: stock/views.py:1788 msgid "Edit Stock Tracking Entry" msgstr "Lagerbestands-Tracking-Eintrag bearbeiten" -#: stock/views.py:1785 +#: stock/views.py:1798 msgid "Add Stock Tracking Entry" msgstr "Lagerbestands-Tracking-Eintrag hinzufügen" @@ -5020,6 +5154,40 @@ msgstr "Suche" msgid "Build Order Settings" msgstr "Bauaufträge" +#: templates/InvenTree/settings/category.html:9 +#, fuzzy +#| msgid "Category Details" +msgid "Category Settings" +msgstr "Kategorie-Details" + +#: templates/InvenTree/settings/category.html:25 +#, fuzzy +#| msgid "Edit Part Parameter Template" +msgid "Category Parameter Templates" +msgstr "Teilparametervorlage bearbeiten" + +#: templates/InvenTree/settings/category.html:52 +#, fuzzy +#| msgid "No part parameter templates found" +msgid "No category parameter templates found" +msgstr "Keine Teilparametervorlagen gefunden" + +#: templates/InvenTree/settings/category.html:67 +#, fuzzy +#| msgid "Default Location" +msgid "Default Value" +msgstr "Standard-Lagerort" + +#: templates/InvenTree/settings/category.html:70 +#: templates/InvenTree/settings/part.html:75 +msgid "Edit Template" +msgstr "Vorlage bearbeiten" + +#: templates/InvenTree/settings/category.html:71 +#: templates/InvenTree/settings/part.html:76 +msgid "Delete Template" +msgstr "Vorlage löschen" + #: templates/InvenTree/settings/global.html:10 #, fuzzy #| msgid "InvenTree Version" @@ -5038,24 +5206,16 @@ msgstr "Einstellungen" msgid "Part Options" msgstr "Quell-Standort" -#: templates/InvenTree/settings/part.html:33 +#: templates/InvenTree/settings/part.html:34 #, fuzzy #| msgid "Edit Part Parameter Template" msgid "Part Parameter Templates" msgstr "Teilparametervorlage bearbeiten" -#: templates/InvenTree/settings/part.html:54 +#: templates/InvenTree/settings/part.html:55 msgid "No part parameter templates found" msgstr "Keine Teilparametervorlagen gefunden" -#: templates/InvenTree/settings/part.html:74 -msgid "Edit Template" -msgstr "Vorlage bearbeiten" - -#: templates/InvenTree/settings/part.html:75 -msgid "Delete Template" -msgstr "Vorlage löschen" - #: templates/InvenTree/settings/po.html:9 #, fuzzy #| msgid "Purchase Order Details" @@ -5114,6 +5274,12 @@ msgstr "InvenTree-Version" msgid "Global" msgstr "" +#: templates/InvenTree/settings/tabs.html:19 +#, fuzzy +#| msgid "Part Categories" +msgid "Categories" +msgstr "Teile-Kategorien" + #: templates/InvenTree/settings/theme.html:10 #, fuzzy #| msgid "Settings" @@ -5491,15 +5657,15 @@ msgstr "Link" msgid "No purchase orders found" msgstr "Keine Bestellungen gefunden" -#: templates/js/order.js:180 templates/js/stock.js:677 +#: templates/js/order.js:181 templates/js/stock.js:677 msgid "Date" msgstr "Datum" -#: templates/js/order.js:210 +#: templates/js/order.js:211 msgid "No sales orders found" msgstr "Keine Aufträge gefunden" -#: templates/js/order.js:267 +#: templates/js/order.js:268 msgid "Shipment Date" msgstr "Versanddatum" @@ -6009,41 +6175,41 @@ msgstr "Revision" msgid "Important dates" msgstr "Stückliste importieren" -#: users/models.py:131 +#: users/models.py:135 msgid "Permission set" msgstr "" -#: users/models.py:139 +#: users/models.py:143 msgid "Group" msgstr "" -#: users/models.py:142 +#: users/models.py:146 msgid "View" msgstr "" -#: users/models.py:142 +#: users/models.py:146 msgid "Permission to view items" msgstr "" -#: users/models.py:144 +#: users/models.py:148 #, fuzzy #| msgid "Address" msgid "Add" msgstr "Adresse" -#: users/models.py:144 +#: users/models.py:148 msgid "Permission to add items" msgstr "" -#: users/models.py:146 +#: users/models.py:150 msgid "Change" msgstr "" -#: users/models.py:146 +#: users/models.py:150 msgid "Permissions to edit items" msgstr "" -#: users/models.py:148 +#: users/models.py:152 #, fuzzy #| msgid "Remove selected BOM items" msgid "Permission to delete items" @@ -6091,11 +6257,6 @@ msgstr "Ausgewählte Stücklistenpositionen entfernen" #~ msgid "New Currency" #~ msgstr "Währung entfernen" -#, fuzzy -#~| msgid "Edit Currency" -#~ msgid "Currency" -#~ msgstr "Währung bearbeiten" - #, fuzzy #~| msgid "Serial Number" #~ msgid "Serial Numbers" @@ -6274,9 +6435,6 @@ msgstr "Ausgewählte Stücklistenpositionen entfernen" #~ msgid "Base Price (Flat Fee)" #~ msgstr "Grundpreis" -#~ msgid "Price Breaks" -#~ msgstr "Preisstaffelung" - #~ msgid "New Price Break" #~ msgstr "Neue Preisstaffelung" diff --git a/InvenTree/locale/en/LC_MESSAGES/django.po b/InvenTree/locale/en/LC_MESSAGES/django.po index 976e28c2c6..f6cb0f4604 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-11-10 13:31+0000\n" +"POT-Creation-Date: 2020-11-12 22:05+1100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -26,27 +26,31 @@ msgstr "" msgid "No matching action found" msgstr "" -#: InvenTree/forms.py:107 build/forms.py:82 build/forms.py:170 +#: InvenTree/forms.py:108 build/forms.py:82 build/forms.py:170 msgid "Confirm" msgstr "" -#: InvenTree/forms.py:123 +#: InvenTree/forms.py:124 msgid "Confirm item deletion" msgstr "" -#: InvenTree/forms.py:155 +#: InvenTree/forms.py:156 msgid "Enter new password" msgstr "" -#: InvenTree/forms.py:162 +#: InvenTree/forms.py:163 msgid "Confirm new password" msgstr "" -#: InvenTree/forms.py:197 +#: InvenTree/forms.py:198 msgid "Apply Theme" msgstr "" -#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:269 +#: InvenTree/forms.py:228 +msgid "Select Category" +msgstr "" + +#: InvenTree/helpers.py:361 order/models.py:189 order/models.py:271 msgid "Invalid quantity provided" msgstr "" @@ -99,19 +103,19 @@ msgstr "" msgid "Description (optional)" msgstr "" -#: InvenTree/settings.py:350 +#: InvenTree/settings.py:354 msgid "English" msgstr "" -#: InvenTree/settings.py:351 +#: InvenTree/settings.py:355 msgid "German" msgstr "" -#: InvenTree/settings.py:352 +#: InvenTree/settings.py:356 msgid "French" msgstr "" -#: InvenTree/settings.py:353 +#: InvenTree/settings.py:357 msgid "Polish" msgstr "" @@ -172,57 +176,61 @@ msgstr "" msgid "Production" msgstr "" -#: InvenTree/validators.py:39 +#: InvenTree/validators.py:22 +msgid "Not a valid currency code" +msgstr "" + +#: InvenTree/validators.py:50 msgid "Invalid character in part name" msgstr "" -#: InvenTree/validators.py:52 +#: InvenTree/validators.py:63 msgid "IPN must match regex pattern" msgstr "" -#: InvenTree/validators.py:66 InvenTree/validators.py:80 -#: InvenTree/validators.py:94 +#: InvenTree/validators.py:77 InvenTree/validators.py:91 +#: InvenTree/validators.py:105 msgid "Reference must match pattern" msgstr "" -#: InvenTree/validators.py:102 +#: InvenTree/validators.py:113 #, python-brace-format msgid "Illegal character in name ({x})" msgstr "" -#: InvenTree/validators.py:121 InvenTree/validators.py:137 +#: InvenTree/validators.py:132 InvenTree/validators.py:148 msgid "Overage value must not be negative" msgstr "" -#: InvenTree/validators.py:139 +#: InvenTree/validators.py:150 msgid "Overage must not exceed 100%" msgstr "" -#: InvenTree/validators.py:146 +#: InvenTree/validators.py:157 msgid "Overage must be an integer value or a percentage" msgstr "" -#: InvenTree/views.py:493 +#: InvenTree/views.py:494 msgid "Delete Item" msgstr "" -#: InvenTree/views.py:542 +#: InvenTree/views.py:543 msgid "Check box to confirm item deletion" msgstr "" -#: InvenTree/views.py:557 templates/InvenTree/settings/user.html:18 +#: InvenTree/views.py:558 templates/InvenTree/settings/user.html:18 msgid "Edit User Information" msgstr "" -#: InvenTree/views.py:568 templates/InvenTree/settings/user.html:22 +#: InvenTree/views.py:569 templates/InvenTree/settings/user.html:22 msgid "Set Password" msgstr "" -#: InvenTree/views.py:587 +#: InvenTree/views.py:588 msgid "Password fields must match" msgstr "" -#: InvenTree/views.py:757 +#: InvenTree/views.py:794 msgid "Database Statistics" msgstr "" @@ -272,10 +280,10 @@ msgstr "" #: build/forms.py:70 build/templates/build/auto_allocate.html:17 #: build/templates/build/build_base.html:78 -#: build/templates/build/detail.html:29 -#: company/templates/company/supplier_part_pricing.html:75 +#: build/templates/build/detail.html:29 common/models.py:488 +#: company/forms.py:112 company/templates/company/supplier_part_pricing.html:75 #: order/templates/order/order_wizard/select_parts.html:32 -#: order/templates/order/purchase_order_detail.html:178 +#: order/templates/order/purchase_order_detail.html:179 #: order/templates/order/sales_order_detail.html:74 #: order/templates/order/sales_order_detail.html:156 #: part/templates/part/allocation.html:16 @@ -351,7 +359,7 @@ msgstr "" #: build/models.py:57 build/templates/build/index.html:6 #: build/templates/build/index.html:14 order/templates/order/so_builds.html:11 #: order/templates/order/so_tabs.html:9 part/templates/part/tabs.html:31 -#: templates/InvenTree/settings/tabs.html:25 users/models.py:30 +#: templates/InvenTree/settings/tabs.html:28 users/models.py:30 msgid "Build Orders" msgstr "" @@ -359,19 +367,20 @@ msgstr "" msgid "Build Order Reference" msgstr "" -#: build/models.py:73 order/templates/order/purchase_order_detail.html:173 +#: build/models.py:73 order/templates/order/purchase_order_detail.html:174 #: templates/js/bom.js:181 templates/js/build.js:493 msgid "Reference" msgstr "" #: build/models.py:80 build/templates/build/detail.html:19 +#: company/templates/company/detail.html:23 #: company/templates/company/supplier_part_base.html:61 #: company/templates/company/supplier_part_detail.html:27 -#: order/templates/order/purchase_order_detail.html:160 +#: order/templates/order/purchase_order_detail.html:161 #: part/templates/part/detail.html:51 part/templates/part/set_category.html:14 #: templates/InvenTree/search.html:147 templates/js/bom.js:174 #: templates/js/bom.js:499 templates/js/build.js:642 templates/js/company.js:56 -#: templates/js/order.js:167 templates/js/order.js:249 templates/js/part.js:188 +#: templates/js/order.js:168 templates/js/order.js:250 templates/js/part.js:188 #: templates/js/part.js:271 templates/js/part.js:391 templates/js/part.js:572 #: templates/js/stock.js:494 templates/js/stock.js:706 msgid "Description" @@ -392,10 +401,10 @@ msgstr "" #: build/models.py:97 build/templates/build/auto_allocate.html:16 #: build/templates/build/build_base.html:73 -#: build/templates/build/detail.html:24 order/models.py:519 +#: build/templates/build/detail.html:24 order/models.py:530 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:148 -#: order/templates/order/receive_parts.html:19 part/models.py:294 +#: order/templates/order/receive_parts.html:19 part/models.py:315 #: part/templates/part/part_app_base.html:7 part/templates/part/related.html:26 #: part/templates/part/set_category.html:13 templates/InvenTree/search.html:133 #: templates/js/barcode.js:336 templates/js/bom.js:147 templates/js/bom.js:484 @@ -475,13 +484,13 @@ msgstr "" msgid "External Link" msgstr "" -#: build/models.py:177 part/models.py:609 stock/models.py:386 +#: build/models.py:177 part/models.py:672 stock/models.py:386 msgid "Link to external URL" msgstr "" -#: build/models.py:181 build/templates/build/tabs.html:23 company/models.py:314 +#: build/models.py:181 build/templates/build/tabs.html:23 company/models.py:344 #: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:18 -#: order/templates/order/purchase_order_detail.html:203 +#: order/templates/order/purchase_order_detail.html:213 #: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:73 #: stock/forms.py:307 stock/forms.py:339 stock/forms.py:367 stock/models.py:448 #: stock/models.py:1432 stock/templates/stock/tabs.html:26 @@ -528,11 +537,11 @@ msgstr "" msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" msgstr "" -#: build/models.py:908 order/models.py:603 +#: build/models.py:908 order/models.py:614 msgid "StockItem is over-allocated" msgstr "" -#: build/models.py:912 order/models.py:606 +#: build/models.py:912 order/models.py:617 msgid "Allocation quantity must be greater than zero" msgstr "" @@ -676,7 +685,7 @@ msgstr "" #: order/templates/order/receive_parts.html:24 #: stock/templates/stock/item_base.html:312 templates/InvenTree/search.html:175 #: templates/js/barcode.js:42 templates/js/build.js:675 -#: templates/js/order.js:172 templates/js/order.js:254 +#: templates/js/order.js:173 templates/js/order.js:255 #: templates/js/stock.js:557 templates/js/stock.js:961 msgid "Status" msgstr "" @@ -687,13 +696,13 @@ msgid "Progress" msgstr "" #: build/templates/build/build_base.html:101 -#: build/templates/build/detail.html:82 order/models.py:517 +#: build/templates/build/detail.html:82 order/models.py:528 #: order/templates/order/sales_order_base.html:9 #: order/templates/order/sales_order_base.html:33 #: order/templates/order/sales_order_notes.html:10 #: order/templates/order/sales_order_ship.html:25 #: part/templates/part/allocation.html:27 -#: stock/templates/stock/item_base.html:221 templates/js/order.js:221 +#: stock/templates/stock/item_base.html:221 templates/js/order.js:222 msgid "Sales Order" msgstr "" @@ -886,7 +895,7 @@ msgstr "" msgid "Create Build Output" msgstr "" -#: build/views.py:207 stock/models.py:827 stock/views.py:1647 +#: build/views.py:207 stock/models.py:827 stock/views.py:1660 msgid "Serial numbers already exist" msgstr "" @@ -902,7 +911,7 @@ msgstr "" msgid "Confirm unallocation of build stock" msgstr "" -#: build/views.py:303 build/views.py:388 stock/views.py:413 +#: build/views.py:303 build/views.py:388 stock/views.py:417 msgid "Check the confirmation box" msgstr "" @@ -991,8 +1000,8 @@ msgstr "" msgid "Add Build Order Attachment" msgstr "" -#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:166 -#: stock/views.py:176 +#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:168 +#: stock/views.py:180 msgid "Added attachment" msgstr "" @@ -1008,167 +1017,179 @@ msgstr "" msgid "Delete Attachment" msgstr "" -#: build/views.py:1122 order/views.py:240 order/views.py:255 stock/views.py:234 +#: build/views.py:1122 order/views.py:240 order/views.py:255 stock/views.py:238 msgid "Deleted attachment" msgstr "" -#: common/models.py:55 +#: common/models.py:56 msgid "InvenTree Instance Name" msgstr "" -#: common/models.py:57 +#: common/models.py:58 msgid "String descriptor for the server instance" msgstr "" -#: common/models.py:61 company/models.py:89 company/models.py:90 +#: common/models.py:62 company/models.py:95 company/models.py:96 msgid "Company name" msgstr "" -#: common/models.py:62 +#: common/models.py:63 msgid "Internal company name" msgstr "" -#: common/models.py:67 +#: common/models.py:68 msgid "Default Currency" msgstr "" -#: common/models.py:68 +#: common/models.py:69 msgid "Default currency" msgstr "" -#: common/models.py:74 +#: common/models.py:75 msgid "IPN Regex" msgstr "" -#: common/models.py:75 +#: common/models.py:76 msgid "Regular expression pattern for matching Part IPN" msgstr "" -#: common/models.py:79 +#: common/models.py:80 msgid "Allow Duplicate IPN" msgstr "" -#: common/models.py:80 +#: common/models.py:81 msgid "Allow multiple parts to share the same IPN" msgstr "" -#: common/models.py:86 +#: common/models.py:87 msgid "Copy Part BOM Data" msgstr "" -#: common/models.py:87 +#: common/models.py:88 msgid "Copy BOM data by default when duplicating a part" msgstr "" -#: common/models.py:93 +#: common/models.py:94 msgid "Copy Part Parameter Data" msgstr "" -#: common/models.py:94 +#: common/models.py:95 msgid "Copy parameter data by default when duplicating a part" msgstr "" -#: common/models.py:100 +#: common/models.py:101 msgid "Copy Part Test Data" msgstr "" -#: common/models.py:101 +#: common/models.py:102 msgid "Copy test data by default when duplicating a part" msgstr "" -#: common/models.py:107 part/models.py:680 part/templates/part/detail.html:168 +#: common/models.py:108 +msgid "Copy Category Parameter Templates" +msgstr "" + +#: common/models.py:109 +msgid "Copy category parameter templates when creating a part" +msgstr "" + +#: common/models.py:115 part/models.py:743 part/templates/part/detail.html:168 #: templates/js/table_filters.js:264 msgid "Component" msgstr "" -#: common/models.py:108 +#: common/models.py:116 msgid "Parts can be used as sub-components by default" msgstr "" -#: common/models.py:114 part/models.py:691 part/templates/part/detail.html:188 +#: common/models.py:122 part/models.py:754 part/templates/part/detail.html:188 msgid "Purchaseable" msgstr "" -#: common/models.py:115 +#: common/models.py:123 msgid "Parts are purchaseable by default" msgstr "" -#: common/models.py:121 part/models.py:696 part/templates/part/detail.html:198 +#: common/models.py:129 part/models.py:759 part/templates/part/detail.html:198 #: templates/js/table_filters.js:272 msgid "Salable" msgstr "" -#: common/models.py:122 +#: common/models.py:130 msgid "Parts are salable by default" msgstr "" -#: common/models.py:128 part/models.py:686 part/templates/part/detail.html:178 +#: common/models.py:136 part/models.py:749 part/templates/part/detail.html:178 #: templates/js/table_filters.js:31 templates/js/table_filters.js:276 msgid "Trackable" msgstr "" -#: common/models.py:129 +#: common/models.py:137 msgid "Parts are trackable by default" msgstr "" -#: common/models.py:135 +#: common/models.py:143 msgid "Build Order Reference Prefix" msgstr "" -#: common/models.py:136 +#: common/models.py:144 msgid "Prefix value for build order reference" msgstr "" -#: common/models.py:141 +#: common/models.py:149 msgid "Build Order Reference Regex" msgstr "" -#: common/models.py:142 +#: common/models.py:150 msgid "Regular expression pattern for matching build order reference" msgstr "" -#: common/models.py:146 +#: common/models.py:154 msgid "Sales Order Reference Prefix" msgstr "" -#: common/models.py:147 +#: common/models.py:155 msgid "Prefix value for sales order reference" msgstr "" -#: common/models.py:151 +#: common/models.py:159 msgid "Purchase Order Reference Prefix" msgstr "" -#: common/models.py:152 +#: common/models.py:160 msgid "Prefix value for purchase order reference" msgstr "" -#: common/models.py:357 +#: common/models.py:373 msgid "Settings key (must be unique - case insensitive" msgstr "" -#: common/models.py:359 +#: common/models.py:375 msgid "Settings value" msgstr "" -#: common/models.py:415 +#: common/models.py:431 msgid "Value must be a boolean value" msgstr "" -#: common/models.py:429 +#: common/models.py:445 msgid "Key string must be unique" msgstr "" -#: common/models.py:474 company/templates/company/supplier_part_pricing.html:80 +#: common/models.py:489 company/forms.py:113 +msgid "Price break quantity" +msgstr "" + +#: common/models.py:497 company/templates/company/supplier_part_pricing.html:80 #: part/templates/part/sale_prices.html:87 templates/js/bom.js:234 msgid "Price" msgstr "" -#: common/models.py:475 +#: common/models.py:498 msgid "Unit price at specified quantity" msgstr "" -#: common/models.py:498 +#: common/models.py:521 msgid "Default" msgstr "" @@ -1180,109 +1201,125 @@ msgstr "" msgid "Change Setting" msgstr "" -#: company/models.py:92 -msgid "Company description" +#: company/forms.py:37 company/models.py:139 +msgid "Default currency used for this company" msgstr "" -#: company/models.py:92 -msgid "Description of the company" +#: company/forms.py:80 +msgid "Single Price" msgstr "" -#: company/models.py:94 company/templates/company/company_base.html:57 -#: templates/js/company.js:61 -msgid "Website" -msgstr "" - -#: company/models.py:94 -msgid "Company website URL" -msgstr "" - -#: company/models.py:97 company/templates/company/company_base.html:64 -msgid "Address" +#: company/forms.py:82 +msgid "Single quantity price" msgstr "" #: company/models.py:98 +msgid "Company description" +msgstr "" + +#: company/models.py:98 +msgid "Description of the company" +msgstr "" + +#: company/models.py:100 company/templates/company/company_base.html:57 +#: company/templates/company/detail.html:28 templates/js/company.js:61 +msgid "Website" +msgstr "" + +#: company/models.py:100 +msgid "Company website URL" +msgstr "" + +#: company/models.py:103 company/templates/company/company_base.html:64 +msgid "Address" +msgstr "" + +#: company/models.py:104 msgid "Company address" msgstr "" -#: company/models.py:101 +#: company/models.py:107 msgid "Phone number" msgstr "" -#: company/models.py:102 +#: company/models.py:108 msgid "Contact phone number" msgstr "" -#: company/models.py:105 company/templates/company/company_base.html:78 +#: company/models.py:111 company/templates/company/company_base.html:78 msgid "Email" msgstr "" -#: company/models.py:105 +#: company/models.py:111 msgid "Contact email address" msgstr "" -#: company/models.py:108 company/templates/company/company_base.html:85 +#: company/models.py:114 company/templates/company/company_base.html:85 msgid "Contact" msgstr "" -#: company/models.py:109 +#: company/models.py:115 msgid "Point of contact" msgstr "" -#: company/models.py:111 +#: company/models.py:117 msgid "Link to external company information" msgstr "" -#: company/models.py:123 +#: company/models.py:129 msgid "Do you sell items to this company?" msgstr "" -#: company/models.py:125 +#: company/models.py:131 msgid "Do you purchase items from this company?" msgstr "" -#: company/models.py:127 +#: company/models.py:133 msgid "Does this company manufacture parts?" msgstr "" -#: company/models.py:283 stock/models.py:338 +#: company/models.py:137 company/templates/company/detail.html:37 +msgid "Currency" +msgstr "" + +#: company/models.py:313 stock/models.py:338 #: stock/templates/stock/item_base.html:177 msgid "Base Part" msgstr "" -#: company/models.py:288 +#: company/models.py:318 msgid "Select part" msgstr "" -#: company/models.py:294 +#: company/models.py:324 msgid "Select supplier" msgstr "" -#: company/models.py:297 +#: company/models.py:327 msgid "Supplier stock keeping unit" msgstr "" -#: company/models.py:304 +#: company/models.py:334 msgid "Select manufacturer" msgstr "" -#: company/models.py:308 +#: company/models.py:338 msgid "Manufacturer part number" msgstr "" -#: company/models.py:310 +#: company/models.py:340 msgid "URL for external supplier part link" msgstr "" -#: company/models.py:312 +#: company/models.py:342 msgid "Supplier part description" msgstr "" -#: company/models.py:316 +#: company/models.py:346 msgid "Minimum charge (e.g. stocking fee)" msgstr "" -#: company/models.py:318 +#: company/models.py:348 msgid "Part packaging" msgstr "" @@ -1305,27 +1342,39 @@ msgstr "" msgid "Phone" msgstr "" -#: company/templates/company/detail.html:16 +#: company/templates/company/detail.html:18 +msgid "Company Name" +msgstr "" + +#: company/templates/company/detail.html:31 +msgid "No website specified" +msgstr "" + +#: company/templates/company/detail.html:40 +msgid "Uses default currency" +msgstr "" + +#: company/templates/company/detail.html:52 #: company/templates/company/supplier_part_base.html:84 #: company/templates/company/supplier_part_detail.html:30 part/bom.py:172 #: templates/js/company.js:44 templates/js/company.js:188 msgid "Manufacturer" msgstr "" -#: company/templates/company/detail.html:21 +#: company/templates/company/detail.html:57 #: company/templates/company/supplier_part_base.html:74 #: company/templates/company/supplier_part_detail.html:21 #: order/templates/order/order_base.html:79 #: order/templates/order/order_wizard/select_pos.html:30 part/bom.py:170 #: stock/templates/stock/item_base.html:287 templates/js/company.js:48 -#: templates/js/company.js:164 templates/js/order.js:154 +#: templates/js/company.js:164 templates/js/order.js:155 msgid "Supplier" msgstr "" -#: company/templates/company/detail.html:26 +#: company/templates/company/detail.html:62 #: order/templates/order/sales_order_base.html:81 stock/models.py:373 #: stock/models.py:374 stock/templates/stock/item_base.html:204 -#: templates/js/company.js:40 templates/js/order.js:236 +#: templates/js/company.js:40 templates/js/order.js:237 msgid "Customer" msgstr "" @@ -1372,21 +1421,21 @@ msgstr "" msgid "Create new Part" msgstr "" -#: company/templates/company/detail_part.html:69 company/views.py:55 +#: company/templates/company/detail_part.html:69 company/views.py:56 #: part/templates/part/supplier.html:47 msgid "New Supplier" msgstr "" -#: company/templates/company/detail_part.html:70 company/views.py:194 +#: company/templates/company/detail_part.html:70 company/views.py:195 msgid "Create new Supplier" msgstr "" -#: company/templates/company/detail_part.html:75 company/views.py:62 +#: company/templates/company/detail_part.html:75 company/views.py:63 #: part/templates/part/supplier.html:53 msgid "New Manufacturer" msgstr "" -#: company/templates/company/detail_part.html:76 company/views.py:197 +#: company/templates/company/detail_part.html:76 company/views.py:198 msgid "Create new Manufacturer" msgstr "" @@ -1420,7 +1469,7 @@ msgstr "" #: order/templates/order/purchase_orders.html:7 #: order/templates/order/purchase_orders.html:12 #: part/templates/part/orders.html:9 part/templates/part/tabs.html:48 -#: templates/InvenTree/settings/tabs.html:28 templates/navbar.html:33 +#: templates/InvenTree/settings/tabs.html:31 templates/navbar.html:33 #: users/models.py:31 msgid "Purchase Orders" msgstr "" @@ -1440,7 +1489,7 @@ msgstr "" #: order/templates/order/sales_orders.html:7 #: order/templates/order/sales_orders.html:12 #: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:56 -#: templates/InvenTree/settings/tabs.html:31 templates/navbar.html:42 +#: templates/InvenTree/settings/tabs.html:34 templates/navbar.html:42 #: users/models.py:32 msgid "Sales Orders" msgstr "" @@ -1513,8 +1562,8 @@ msgstr "" msgid "Pricing Information" msgstr "" -#: company/templates/company/supplier_part_pricing.html:17 company/views.py:412 -#: part/templates/part/sale_prices.html:14 part/views.py:2350 +#: company/templates/company/supplier_part_pricing.html:17 company/views.py:459 +#: part/templates/part/sale_prices.html:14 part/views.py:2546 msgid "Add Price Break" msgstr "" @@ -1544,7 +1593,7 @@ msgstr "" #: company/templates/company/supplier_part_tabs.html:8 #: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18 #: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155 -#: templates/InvenTree/settings/tabs.html:22 templates/js/part.js:192 +#: templates/InvenTree/settings/tabs.html:25 templates/js/part.js:192 #: templates/js/part.js:418 templates/js/stock.js:502 templates/navbar.html:22 #: users/models.py:29 msgid "Stock" @@ -1555,96 +1604,96 @@ msgid "Orders" msgstr "" #: company/templates/company/tabs.html:9 -#: order/templates/order/receive_parts.html:14 part/models.py:295 +#: order/templates/order/receive_parts.html:14 part/models.py:316 #: part/templates/part/cat_link.html:7 part/templates/part/category.html:94 #: part/templates/part/category_tabs.html:6 -#: templates/InvenTree/settings/tabs.html:19 templates/navbar.html:19 +#: templates/InvenTree/settings/tabs.html:22 templates/navbar.html:19 #: templates/stats.html:8 templates/stats.html:17 users/models.py:28 msgid "Parts" msgstr "" -#: company/views.py:54 part/templates/part/tabs.html:42 +#: company/views.py:55 part/templates/part/tabs.html:42 #: templates/navbar.html:31 msgid "Suppliers" msgstr "" -#: company/views.py:61 templates/navbar.html:32 +#: company/views.py:62 templates/navbar.html:32 msgid "Manufacturers" msgstr "" -#: company/views.py:68 templates/navbar.html:41 +#: company/views.py:69 templates/navbar.html:41 msgid "Customers" msgstr "" -#: company/views.py:69 +#: company/views.py:70 msgid "New Customer" msgstr "" -#: company/views.py:77 +#: company/views.py:78 msgid "Companies" msgstr "" -#: company/views.py:78 +#: company/views.py:79 msgid "New Company" msgstr "" -#: company/views.py:156 +#: company/views.py:157 msgid "Update Company Image" msgstr "" -#: company/views.py:162 +#: company/views.py:163 msgid "Updated company image" msgstr "" -#: company/views.py:172 +#: company/views.py:173 msgid "Edit Company" msgstr "" -#: company/views.py:177 +#: company/views.py:178 msgid "Edited company information" msgstr "" -#: company/views.py:200 +#: company/views.py:201 msgid "Create new Customer" msgstr "" -#: company/views.py:202 +#: company/views.py:203 msgid "Create new Company" msgstr "" -#: company/views.py:229 +#: company/views.py:230 msgid "Created new company" msgstr "" -#: company/views.py:239 +#: company/views.py:240 msgid "Delete Company" msgstr "" -#: company/views.py:245 +#: company/views.py:246 msgid "Company was deleted" msgstr "" -#: company/views.py:270 +#: company/views.py:271 msgid "Edit Supplier Part" msgstr "" -#: company/views.py:280 templates/js/stock.js:846 +#: company/views.py:289 templates/js/stock.js:846 msgid "Create new Supplier Part" msgstr "" -#: company/views.py:341 +#: company/views.py:388 msgid "Delete Supplier Part" msgstr "" -#: company/views.py:418 part/views.py:2356 +#: company/views.py:465 part/views.py:2552 msgid "Added new price break" msgstr "" -#: company/views.py:454 part/views.py:2400 +#: company/views.py:521 part/views.py:2596 msgid "Edit Price Break" msgstr "" -#: company/views.py:470 part/views.py:2416 +#: company/views.py:537 part/views.py:2612 msgid "Delete Price Break" msgstr "" @@ -1701,114 +1750,123 @@ msgstr "" msgid "Enter sales order number" msgstr "" -#: order/models.py:108 +#: order/models.py:110 msgid "Order reference" msgstr "" -#: order/models.py:110 +#: order/models.py:112 msgid "Order description" msgstr "" -#: order/models.py:112 +#: order/models.py:114 msgid "Link to external page" msgstr "" -#: order/models.py:122 +#: order/models.py:124 msgid "Order notes" msgstr "" -#: order/models.py:140 order/models.py:326 +#: order/models.py:142 order/models.py:328 msgid "Purchase order status" msgstr "" -#: order/models.py:148 +#: order/models.py:150 msgid "Company from which the items are being ordered" msgstr "" -#: order/models.py:151 +#: order/models.py:153 msgid "Supplier order reference code" msgstr "" -#: order/models.py:160 +#: order/models.py:162 msgid "Date order was issued" msgstr "" -#: order/models.py:162 +#: order/models.py:164 msgid "Date order was completed" msgstr "" -#: order/models.py:185 order/models.py:267 part/views.py:1479 +#: order/models.py:187 order/models.py:269 part/views.py:1496 #: stock/models.py:244 stock/models.py:811 msgid "Quantity must be greater than zero" msgstr "" -#: order/models.py:190 +#: order/models.py:192 msgid "Part supplier must match PO supplier" msgstr "" -#: order/models.py:262 +#: order/models.py:264 msgid "Lines can only be received against an order marked as 'Placed'" msgstr "" -#: order/models.py:322 +#: order/models.py:324 msgid "Company to which the items are being sold" msgstr "" -#: order/models.py:328 +#: order/models.py:330 msgid "Customer order reference code" msgstr "" -#: order/models.py:367 +#: order/models.py:369 msgid "SalesOrder cannot be shipped as it is not currently pending" msgstr "" -#: order/models.py:454 +#: order/models.py:456 msgid "Item quantity" msgstr "" -#: order/models.py:456 +#: order/models.py:458 msgid "Line item reference" msgstr "" -#: order/models.py:458 +#: order/models.py:460 msgid "Line item notes" msgstr "" -#: order/models.py:484 order/templates/order/order_base.html:9 +#: order/models.py:486 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 #: stock/templates/stock/item_base.html:259 templates/js/order.js:139 msgid "Purchase Order" msgstr "" -#: order/models.py:497 +#: order/models.py:499 msgid "Supplier part" msgstr "" -#: order/models.py:500 +#: order/models.py:502 msgid "Number of items received" msgstr "" -#: order/models.py:594 +#: order/models.py:509 stock/models.py:457 +#: stock/templates/stock/item_base.html:266 +msgid "Purchase Price" +msgstr "" + +#: order/models.py:510 +msgid "Unit purchase price" +msgstr "" + +#: order/models.py:605 msgid "Cannot allocate stock item to a line with a different part" msgstr "" -#: order/models.py:596 +#: order/models.py:607 msgid "Cannot allocate stock to a line without a part" msgstr "" -#: order/models.py:599 +#: order/models.py:610 msgid "Allocation quantity cannot exceed stock quantity" msgstr "" -#: order/models.py:609 +#: order/models.py:620 msgid "Quantity must be 1 for serialized stock item" msgstr "" -#: order/models.py:625 +#: order/models.py:636 msgid "Select stock item to allocate" msgstr "" -#: order/models.py:628 +#: order/models.py:639 msgid "Enter stock allocation quantity" msgstr "" @@ -1844,7 +1902,7 @@ msgstr "" msgid "Order Status" msgstr "" -#: order/templates/order/order_base.html:85 templates/js/order.js:161 +#: order/templates/order/order_base.html:85 templates/js/order.js:162 msgid "Supplier Reference" msgstr "" @@ -1853,7 +1911,7 @@ msgid "Issued" msgstr "" #: order/templates/order/order_base.html:111 -#: order/templates/order/purchase_order_detail.html:183 +#: order/templates/order/purchase_order_detail.html:193 #: order/templates/order/receive_parts.html:22 #: order/templates/order/sales_order_base.html:113 msgid "Received" @@ -1900,7 +1958,7 @@ msgid "Select existing purchase orders, or create new orders." msgstr "" #: order/templates/order/order_wizard/select_pos.html:31 -#: templates/js/order.js:185 templates/js/order.js:272 +#: templates/js/order.js:186 templates/js/order.js:273 msgid "Items" msgstr "" @@ -1952,21 +2010,25 @@ msgstr "" msgid "No line items found" msgstr "" -#: order/templates/order/purchase_order_detail.html:165 +#: order/templates/order/purchase_order_detail.html:166 #: order/templates/order/receive_parts.html:20 msgid "Order Code" msgstr "" -#: order/templates/order/purchase_order_detail.html:214 +#: order/templates/order/purchase_order_detail.html:184 +msgid "Unit Price" +msgstr "" + +#: order/templates/order/purchase_order_detail.html:225 #: order/templates/order/sales_order_detail.html:285 msgid "Edit line item" msgstr "" -#: order/templates/order/purchase_order_detail.html:215 +#: order/templates/order/purchase_order_detail.html:226 msgid "Delete line item" msgstr "" -#: order/templates/order/purchase_order_detail.html:220 +#: order/templates/order/purchase_order_detail.html:231 msgid "Receive line item" msgstr "" @@ -2003,7 +2065,7 @@ msgstr "" msgid "Sales Order Details" msgstr "" -#: order/templates/order/sales_order_base.html:87 templates/js/order.js:243 +#: order/templates/order/sales_order_base.html:87 templates/js/order.js:244 msgid "Customer Reference" msgstr "" @@ -2256,367 +2318,389 @@ msgstr "" msgid "Error reading BOM file (incorrect row size)" msgstr "" -#: part/forms.py:60 stock/forms.py:255 +#: part/forms.py:61 stock/forms.py:255 msgid "File Format" msgstr "" -#: part/forms.py:60 stock/forms.py:255 +#: part/forms.py:61 stock/forms.py:255 msgid "Select output file format" msgstr "" -#: part/forms.py:62 +#: part/forms.py:63 msgid "Cascading" msgstr "" -#: part/forms.py:62 +#: part/forms.py:63 msgid "Download cascading / multi-level BOM" msgstr "" -#: part/forms.py:64 +#: part/forms.py:65 msgid "Levels" msgstr "" -#: part/forms.py:64 +#: part/forms.py:65 msgid "Select maximum number of BOM levels to export (0 = all levels)" msgstr "" -#: part/forms.py:66 +#: part/forms.py:67 msgid "Include Parameter Data" msgstr "" -#: part/forms.py:66 +#: part/forms.py:67 msgid "Include part parameters data in exported BOM" msgstr "" -#: part/forms.py:68 +#: part/forms.py:69 msgid "Include Stock Data" msgstr "" -#: part/forms.py:68 +#: part/forms.py:69 msgid "Include part stock data in exported BOM" msgstr "" -#: part/forms.py:70 +#: part/forms.py:71 msgid "Include Supplier Data" msgstr "" -#: part/forms.py:70 +#: part/forms.py:71 msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:91 part/models.py:1644 +#: part/forms.py:92 part/models.py:1715 msgid "Parent Part" msgstr "" -#: part/forms.py:92 part/templates/part/bom_duplicate.html:7 +#: part/forms.py:93 part/templates/part/bom_duplicate.html:7 msgid "Select parent part to copy BOM from" msgstr "" -#: part/forms.py:98 +#: part/forms.py:99 msgid "Clear existing BOM items" msgstr "" -#: part/forms.py:103 +#: part/forms.py:104 msgid "Confirm BOM duplication" msgstr "" -#: part/forms.py:121 +#: part/forms.py:122 msgid "Confirm that the BOM is correct" msgstr "" -#: part/forms.py:133 +#: part/forms.py:134 msgid "Select BOM file to upload" msgstr "" -#: part/forms.py:152 +#: part/forms.py:153 msgid "Related Part" msgstr "" -#: part/forms.py:171 +#: part/forms.py:172 msgid "Select part category" msgstr "" -#: part/forms.py:187 +#: part/forms.py:188 msgid "Duplicate all BOM data for this part" msgstr "" -#: part/forms.py:188 +#: part/forms.py:189 msgid "Copy BOM" msgstr "" -#: part/forms.py:193 +#: part/forms.py:194 msgid "Duplicate all parameter data for this part" msgstr "" -#: part/forms.py:194 +#: part/forms.py:195 msgid "Copy Parameters" msgstr "" -#: part/forms.py:199 +#: part/forms.py:200 msgid "Confirm part creation" msgstr "" -#: part/forms.py:296 +#: part/forms.py:205 +msgid "Include category parameter templates" +msgstr "" + +#: part/forms.py:210 +msgid "Include parent categories parameter templates" +msgstr "" + +#: part/forms.py:285 +msgid "Add parameter template to same level categories" +msgstr "" + +#: part/forms.py:289 +msgid "Add parameter template to all categories" +msgstr "" + +#: part/forms.py:331 msgid "Input quantity for price calculation" msgstr "" -#: part/models.py:67 +#: part/models.py:68 msgid "Default location for parts in this category" msgstr "" -#: part/models.py:70 +#: part/models.py:71 msgid "Default keywords for parts in this category" msgstr "" -#: part/models.py:76 part/templates/part/part_app_base.html:9 +#: part/models.py:77 part/models.py:1760 +#: part/templates/part/part_app_base.html:9 msgid "Part Category" msgstr "" -#: part/models.py:77 part/templates/part/category.html:18 +#: part/models.py:78 part/templates/part/category.html:18 #: part/templates/part/category.html:89 templates/stats.html:12 msgid "Part Categories" msgstr "" -#: part/models.py:346 part/models.py:356 +#: part/models.py:408 part/models.py:418 #, python-brace-format msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" msgstr "" -#: part/models.py:453 +#: part/models.py:515 msgid "Next available serial numbers are" msgstr "" -#: part/models.py:457 +#: part/models.py:519 msgid "Next available serial number is" msgstr "" -#: part/models.py:462 +#: part/models.py:524 msgid "Most recent serial number is" msgstr "" -#: part/models.py:541 +#: part/models.py:603 msgid "Duplicate IPN not allowed in part settings" msgstr "" -#: part/models.py:552 +#: part/models.py:614 msgid "Part must be unique for name, IPN and revision" msgstr "" -#: part/models.py:581 part/templates/part/detail.html:19 +#: part/models.py:644 part/templates/part/detail.html:19 msgid "Part name" msgstr "" -#: part/models.py:585 +#: part/models.py:648 msgid "Is this part a template part?" msgstr "" -#: part/models.py:594 +#: part/models.py:657 msgid "Is this part a variant of another part?" msgstr "" -#: part/models.py:596 +#: part/models.py:659 msgid "Part description" msgstr "" -#: part/models.py:598 +#: part/models.py:661 msgid "Part keywords to improve visibility in search results" msgstr "" -#: part/models.py:603 +#: part/models.py:666 msgid "Part category" msgstr "" -#: part/models.py:605 +#: part/models.py:668 msgid "Internal Part Number" msgstr "" -#: part/models.py:607 +#: part/models.py:670 msgid "Part revision or version number" msgstr "" -#: part/models.py:621 +#: part/models.py:684 msgid "Where is this item normally stored?" msgstr "" -#: part/models.py:665 +#: part/models.py:728 msgid "Default supplier part" msgstr "" -#: part/models.py:668 +#: part/models.py:731 msgid "Minimum allowed stock level" msgstr "" -#: part/models.py:670 +#: part/models.py:733 msgid "Stock keeping units for this part" msgstr "" -#: part/models.py:674 part/templates/part/detail.html:158 +#: part/models.py:737 part/templates/part/detail.html:158 #: templates/js/table_filters.js:260 msgid "Assembly" msgstr "" -#: part/models.py:675 +#: part/models.py:738 msgid "Can this part be built from other parts?" msgstr "" -#: part/models.py:681 +#: part/models.py:744 msgid "Can this part be used to build other parts?" msgstr "" -#: part/models.py:687 +#: part/models.py:750 msgid "Does this part have tracking for unique items?" msgstr "" -#: part/models.py:692 +#: part/models.py:755 msgid "Can this part be purchased from external suppliers?" msgstr "" -#: part/models.py:697 +#: part/models.py:760 msgid "Can this part be sold to customers?" msgstr "" -#: part/models.py:701 part/templates/part/detail.html:215 +#: part/models.py:764 part/templates/part/detail.html:215 #: templates/js/table_filters.js:19 templates/js/table_filters.js:55 #: templates/js/table_filters.js:186 templates/js/table_filters.js:243 msgid "Active" msgstr "" -#: part/models.py:702 +#: part/models.py:765 msgid "Is this part active?" msgstr "" -#: part/models.py:706 part/templates/part/detail.html:138 +#: part/models.py:769 part/templates/part/detail.html:138 #: templates/js/table_filters.js:27 msgid "Virtual" msgstr "" -#: part/models.py:707 +#: part/models.py:770 msgid "Is this a virtual part, such as a software product or license?" msgstr "" -#: part/models.py:709 +#: part/models.py:772 msgid "Part notes - supports Markdown formatting" msgstr "" -#: part/models.py:711 +#: part/models.py:774 msgid "Stored BOM checksum" msgstr "" -#: part/models.py:1517 +#: part/models.py:1588 msgid "Test templates can only be created for trackable parts" msgstr "" -#: part/models.py:1534 +#: part/models.py:1605 msgid "Test with this name already exists for this part" msgstr "" -#: part/models.py:1553 templates/js/part.js:567 templates/js/stock.js:92 +#: part/models.py:1624 templates/js/part.js:567 templates/js/stock.js:92 msgid "Test Name" msgstr "" -#: part/models.py:1554 +#: part/models.py:1625 msgid "Enter a name for the test" msgstr "" -#: part/models.py:1559 +#: part/models.py:1630 msgid "Test Description" msgstr "" -#: part/models.py:1560 +#: part/models.py:1631 msgid "Enter description for this test" msgstr "" -#: part/models.py:1565 templates/js/part.js:576 +#: part/models.py:1636 templates/js/part.js:576 #: templates/js/table_filters.js:172 msgid "Required" msgstr "" -#: part/models.py:1566 +#: part/models.py:1637 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1571 templates/js/part.js:584 +#: part/models.py:1642 templates/js/part.js:584 msgid "Requires Value" msgstr "" -#: part/models.py:1572 +#: part/models.py:1643 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1577 templates/js/part.js:591 +#: part/models.py:1648 templates/js/part.js:591 msgid "Requires Attachment" msgstr "" -#: part/models.py:1578 +#: part/models.py:1649 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1611 +#: part/models.py:1682 msgid "Parameter template name must be unique" msgstr "" -#: part/models.py:1616 +#: part/models.py:1687 msgid "Parameter Name" msgstr "" -#: part/models.py:1618 +#: part/models.py:1689 msgid "Parameter Units" msgstr "" -#: part/models.py:1646 +#: part/models.py:1717 part/models.py:1765 +#: templates/InvenTree/settings/category.html:62 msgid "Parameter Template" msgstr "" -#: part/models.py:1648 +#: part/models.py:1719 msgid "Parameter Value" msgstr "" -#: part/models.py:1685 +#: part/models.py:1769 +msgid "Default Parameter Value" +msgstr "" + +#: part/models.py:1799 msgid "Select parent part" msgstr "" -#: part/models.py:1693 +#: part/models.py:1807 msgid "Select part to be used in BOM" msgstr "" -#: part/models.py:1699 +#: part/models.py:1813 msgid "BOM quantity for this BOM item" msgstr "" -#: part/models.py:1701 +#: part/models.py:1815 msgid "This BOM item is optional" msgstr "" -#: part/models.py:1704 +#: part/models.py:1818 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "" -#: part/models.py:1707 +#: part/models.py:1821 msgid "BOM item reference" msgstr "" -#: part/models.py:1710 +#: part/models.py:1824 msgid "BOM item notes" msgstr "" -#: part/models.py:1712 +#: part/models.py:1826 msgid "BOM line checksum" msgstr "" -#: part/models.py:1779 part/views.py:1485 part/views.py:1537 +#: part/models.py:1893 part/views.py:1502 part/views.py:1554 #: stock/models.py:234 msgid "Quantity must be integer value for trackable parts" msgstr "" -#: part/models.py:1795 +#: part/models.py:1909 msgid "BOM Item" msgstr "" -#: part/models.py:1910 +#: part/models.py:2024 msgid "Select Related Part" msgstr "" -#: part/models.py:1942 +#: part/models.py:2056 msgid "" "Error creating relationship: check that the part is not related to itself " "and that the relationship is unique" @@ -2704,7 +2788,7 @@ msgstr "" msgid "Validate" msgstr "" -#: part/templates/part/bom.html:62 part/views.py:1776 +#: part/templates/part/bom.html:62 part/views.py:1793 msgid "Export Bill of Materials" msgstr "" @@ -2800,7 +2884,7 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:24 part/views.py:2167 +#: part/templates/part/category.html:24 part/views.py:2184 msgid "Create new part category" msgstr "" @@ -2872,7 +2956,7 @@ msgstr "" msgid "Create new Part Category" msgstr "" -#: part/templates/part/category.html:216 stock/views.py:1338 +#: part/templates/part/category.html:216 stock/views.py:1342 msgid "Create new Stock Location" msgstr "" @@ -2934,7 +3018,7 @@ msgstr "" msgid "Minimum Stock" msgstr "" -#: part/templates/part/detail.html:114 templates/js/order.js:262 +#: part/templates/part/detail.html:114 templates/js/order.js:263 msgid "Creation Date" msgstr "" @@ -3023,7 +3107,9 @@ msgstr "" msgid "Add new parameter" msgstr "" -#: part/templates/part/params.html:15 templates/InvenTree/settings/part.html:37 +#: part/templates/part/params.html:15 +#: templates/InvenTree/settings/category.html:29 +#: templates/InvenTree/settings/part.html:38 msgid "New Parameter" msgstr "" @@ -3033,7 +3119,7 @@ msgid "Value" msgstr "" #: part/templates/part/params.html:41 part/templates/part/related.html:41 -#: part/templates/part/supplier.html:19 users/models.py:148 +#: part/templates/part/supplier.html:19 users/models.py:152 msgid "Delete" msgstr "" @@ -3241,208 +3327,220 @@ msgstr "" msgid "New Variant" msgstr "" -#: part/views.py:82 +#: part/views.py:84 msgid "Add Related Part" msgstr "" -#: part/views.py:138 +#: part/views.py:140 msgid "Delete Related Part" msgstr "" -#: part/views.py:150 +#: part/views.py:152 msgid "Add part attachment" msgstr "" -#: part/views.py:205 templates/attachment_table.html:34 +#: part/views.py:207 templates/attachment_table.html:34 msgid "Edit attachment" msgstr "" -#: part/views.py:211 +#: part/views.py:213 msgid "Part attachment updated" msgstr "" -#: part/views.py:226 +#: part/views.py:228 msgid "Delete Part Attachment" msgstr "" -#: part/views.py:234 +#: part/views.py:236 msgid "Deleted part attachment" msgstr "" -#: part/views.py:243 +#: part/views.py:245 msgid "Create Test Template" msgstr "" -#: part/views.py:272 +#: part/views.py:274 msgid "Edit Test Template" msgstr "" -#: part/views.py:288 +#: part/views.py:290 msgid "Delete Test Template" msgstr "" -#: part/views.py:297 +#: part/views.py:299 msgid "Set Part Category" msgstr "" -#: part/views.py:347 +#: part/views.py:349 #, python-brace-format msgid "Set category for {n} parts" msgstr "" -#: part/views.py:382 +#: part/views.py:384 msgid "Create Variant" msgstr "" -#: part/views.py:464 +#: part/views.py:466 msgid "Duplicate Part" msgstr "" -#: part/views.py:471 +#: part/views.py:473 msgid "Copied part" msgstr "" -#: part/views.py:525 part/views.py:655 +#: part/views.py:527 part/views.py:661 msgid "Possible matches exist - confirm creation of new part" msgstr "" -#: part/views.py:590 templates/js/stock.js:840 +#: part/views.py:592 templates/js/stock.js:840 msgid "Create New Part" msgstr "" -#: part/views.py:597 +#: part/views.py:599 msgid "Created new part" msgstr "" -#: part/views.py:813 +#: part/views.py:830 msgid "Part QR Code" msgstr "" -#: part/views.py:832 +#: part/views.py:849 msgid "Upload Part Image" msgstr "" -#: part/views.py:840 part/views.py:877 +#: part/views.py:857 part/views.py:894 msgid "Updated part image" msgstr "" -#: part/views.py:849 +#: part/views.py:866 msgid "Select Part Image" msgstr "" -#: part/views.py:880 +#: part/views.py:897 msgid "Part image not found" msgstr "" -#: part/views.py:891 +#: part/views.py:908 msgid "Edit Part Properties" msgstr "" -#: part/views.py:918 +#: part/views.py:935 msgid "Duplicate BOM" msgstr "" -#: part/views.py:949 +#: part/views.py:966 msgid "Confirm duplication of BOM from parent" msgstr "" -#: part/views.py:970 +#: part/views.py:987 msgid "Validate BOM" msgstr "" -#: part/views.py:993 +#: part/views.py:1010 msgid "Confirm that the BOM is valid" msgstr "" -#: part/views.py:1004 +#: part/views.py:1021 msgid "Validated Bill of Materials" msgstr "" -#: part/views.py:1138 +#: part/views.py:1155 msgid "No BOM file provided" msgstr "" -#: part/views.py:1488 +#: part/views.py:1505 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1513 part/views.py:1516 +#: part/views.py:1530 part/views.py:1533 msgid "Select valid part" msgstr "" -#: part/views.py:1522 +#: part/views.py:1539 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1560 +#: part/views.py:1577 msgid "Select a part" msgstr "" -#: part/views.py:1566 +#: part/views.py:1583 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1570 +#: part/views.py:1587 msgid "Specify quantity" msgstr "" -#: part/views.py:1826 +#: part/views.py:1843 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1835 +#: part/views.py:1852 msgid "Part was deleted" msgstr "" -#: part/views.py:1844 +#: part/views.py:1861 msgid "Part Pricing" msgstr "" -#: part/views.py:1958 +#: part/views.py:1975 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1968 +#: part/views.py:1985 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1977 +#: part/views.py:1994 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1987 +#: part/views.py:2004 msgid "Create Part Parameter" msgstr "" -#: part/views.py:2039 +#: part/views.py:2056 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:2055 +#: part/views.py:2072 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:2114 +#: part/views.py:2131 msgid "Edit Part Category" msgstr "" -#: part/views.py:2151 +#: part/views.py:2168 msgid "Delete Part Category" msgstr "" -#: part/views.py:2159 +#: part/views.py:2176 msgid "Part category was deleted" msgstr "" -#: part/views.py:2222 +#: part/views.py:2232 +msgid "Create Category Parameter Template" +msgstr "" + +#: part/views.py:2335 +msgid "Edit Category Parameter Template" +msgstr "" + +#: part/views.py:2393 +msgid "Delete Category Parameter Template" +msgstr "" + +#: part/views.py:2418 msgid "Create BOM Item" msgstr "" -#: part/views.py:2290 +#: part/views.py:2486 msgid "Edit BOM item" msgstr "" -#: part/views.py:2340 +#: part/views.py:2536 msgid "Confim BOM item deletion" msgstr "" @@ -3526,7 +3624,7 @@ msgstr "" msgid "Add note (required)" msgstr "" -#: stock/forms.py:371 stock/views.py:916 stock/views.py:1114 +#: stock/forms.py:371 stock/views.py:920 stock/views.py:1118 msgid "Confirm stock adjustment" msgstr "" @@ -3644,10 +3742,6 @@ msgstr "" msgid "Stock Item Notes" msgstr "" -#: stock/models.py:457 stock/templates/stock/item_base.html:266 -msgid "Purchase Price" -msgstr "" - #: stock/models.py:458 msgid "Single unit purchase price at time of purchase" msgstr "" @@ -4041,7 +4135,7 @@ msgstr "" msgid "The following stock items will be uninstalled" msgstr "" -#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1310 +#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1314 msgid "Convert Stock Item" msgstr "" @@ -4073,222 +4167,222 @@ msgstr "" msgid "Installed Items" msgstr "" -#: stock/views.py:119 +#: stock/views.py:123 msgid "Edit Stock Location" msgstr "" -#: stock/views.py:144 +#: stock/views.py:148 msgid "Stock Location QR code" msgstr "" -#: stock/views.py:163 +#: stock/views.py:167 msgid "Add Stock Item Attachment" msgstr "" -#: stock/views.py:210 +#: stock/views.py:214 msgid "Edit Stock Item Attachment" msgstr "" -#: stock/views.py:227 +#: stock/views.py:231 msgid "Delete Stock Item Attachment" msgstr "" -#: stock/views.py:244 +#: stock/views.py:248 msgid "Assign to Customer" msgstr "" -#: stock/views.py:254 +#: stock/views.py:258 msgid "Customer must be specified" msgstr "" -#: stock/views.py:278 +#: stock/views.py:282 msgid "Return to Stock" msgstr "" -#: stock/views.py:288 +#: stock/views.py:292 msgid "Specify a valid location" msgstr "" -#: stock/views.py:299 +#: stock/views.py:303 msgid "Stock item returned from customer" msgstr "" -#: stock/views.py:309 +#: stock/views.py:313 msgid "Select Label Template" msgstr "" -#: stock/views.py:332 +#: stock/views.py:336 msgid "Select valid label" msgstr "" -#: stock/views.py:396 +#: stock/views.py:400 msgid "Delete All Test Data" msgstr "" -#: stock/views.py:412 +#: stock/views.py:416 msgid "Confirm test data deletion" msgstr "" -#: stock/views.py:432 +#: stock/views.py:436 msgid "Add Test Result" msgstr "" -#: stock/views.py:473 +#: stock/views.py:477 msgid "Edit Test Result" msgstr "" -#: stock/views.py:491 +#: stock/views.py:495 msgid "Delete Test Result" msgstr "" -#: stock/views.py:503 +#: stock/views.py:507 msgid "Select Test Report Template" msgstr "" -#: stock/views.py:518 +#: stock/views.py:522 msgid "Select valid template" msgstr "" -#: stock/views.py:571 +#: stock/views.py:575 msgid "Stock Export Options" msgstr "" -#: stock/views.py:693 +#: stock/views.py:697 msgid "Stock Item QR Code" msgstr "" -#: stock/views.py:719 +#: stock/views.py:723 msgid "Install Stock Item" msgstr "" -#: stock/views.py:819 +#: stock/views.py:823 msgid "Uninstall Stock Items" msgstr "" -#: stock/views.py:927 +#: stock/views.py:931 msgid "Uninstalled stock items" msgstr "" -#: stock/views.py:952 +#: stock/views.py:956 msgid "Adjust Stock" msgstr "" -#: stock/views.py:1062 +#: stock/views.py:1066 msgid "Move Stock Items" msgstr "" -#: stock/views.py:1063 +#: stock/views.py:1067 msgid "Count Stock Items" msgstr "" -#: stock/views.py:1064 +#: stock/views.py:1068 msgid "Remove From Stock" msgstr "" -#: stock/views.py:1065 +#: stock/views.py:1069 msgid "Add Stock Items" msgstr "" -#: stock/views.py:1066 +#: stock/views.py:1070 msgid "Delete Stock Items" msgstr "" -#: stock/views.py:1094 +#: stock/views.py:1098 msgid "Must enter integer value" msgstr "" -#: stock/views.py:1099 +#: stock/views.py:1103 msgid "Quantity must be positive" msgstr "" -#: stock/views.py:1106 +#: stock/views.py:1110 #, python-brace-format msgid "Quantity must not exceed {x}" msgstr "" -#: stock/views.py:1185 +#: stock/views.py:1189 #, python-brace-format msgid "Added stock to {n} items" msgstr "" -#: stock/views.py:1200 +#: stock/views.py:1204 #, python-brace-format msgid "Removed stock from {n} items" msgstr "" -#: stock/views.py:1213 +#: stock/views.py:1217 #, python-brace-format msgid "Counted stock for {n} items" msgstr "" -#: stock/views.py:1241 +#: stock/views.py:1245 msgid "No items were moved" msgstr "" -#: stock/views.py:1244 +#: stock/views.py:1248 #, python-brace-format msgid "Moved {n} items to {dest}" msgstr "" -#: stock/views.py:1263 +#: stock/views.py:1267 #, python-brace-format msgid "Deleted {n} stock items" msgstr "" -#: stock/views.py:1275 +#: stock/views.py:1279 msgid "Edit Stock Item" msgstr "" -#: stock/views.py:1360 +#: stock/views.py:1364 msgid "Serialize Stock" msgstr "" -#: stock/views.py:1454 templates/js/build.js:210 +#: stock/views.py:1458 templates/js/build.js:210 msgid "Create new Stock Item" msgstr "" -#: stock/views.py:1555 +#: stock/views.py:1559 msgid "Duplicate Stock Item" msgstr "" -#: stock/views.py:1621 +#: stock/views.py:1634 msgid "Invalid quantity" msgstr "" -#: stock/views.py:1624 +#: stock/views.py:1637 msgid "Quantity cannot be less than zero" msgstr "" -#: stock/views.py:1628 +#: stock/views.py:1641 msgid "Invalid part selection" msgstr "" -#: stock/views.py:1676 +#: stock/views.py:1689 #, python-brace-format msgid "Created {n} new stock items" msgstr "" -#: stock/views.py:1695 stock/views.py:1711 +#: stock/views.py:1708 stock/views.py:1724 msgid "Created new stock item" msgstr "" -#: stock/views.py:1730 +#: stock/views.py:1743 msgid "Delete Stock Location" msgstr "" -#: stock/views.py:1744 +#: stock/views.py:1757 msgid "Delete Stock Item" msgstr "" -#: stock/views.py:1756 +#: stock/views.py:1769 msgid "Delete Stock Tracking Entry" msgstr "" -#: stock/views.py:1775 +#: stock/views.py:1788 msgid "Edit Stock Tracking Entry" msgstr "" -#: stock/views.py:1785 +#: stock/views.py:1798 msgid "Add Stock Tracking Entry" msgstr "" @@ -4360,6 +4454,32 @@ msgstr "" msgid "Build Order Settings" msgstr "" +#: templates/InvenTree/settings/category.html:9 +msgid "Category Settings" +msgstr "" + +#: templates/InvenTree/settings/category.html:25 +msgid "Category Parameter Templates" +msgstr "" + +#: templates/InvenTree/settings/category.html:52 +msgid "No category parameter templates found" +msgstr "" + +#: templates/InvenTree/settings/category.html:67 +msgid "Default Value" +msgstr "" + +#: templates/InvenTree/settings/category.html:70 +#: templates/InvenTree/settings/part.html:75 +msgid "Edit Template" +msgstr "" + +#: templates/InvenTree/settings/category.html:71 +#: templates/InvenTree/settings/part.html:76 +msgid "Delete Template" +msgstr "" + #: templates/InvenTree/settings/global.html:10 msgid "Global InvenTree Settings" msgstr "" @@ -4372,22 +4492,14 @@ msgstr "" msgid "Part Options" msgstr "" -#: templates/InvenTree/settings/part.html:33 +#: templates/InvenTree/settings/part.html:34 msgid "Part Parameter Templates" msgstr "" -#: templates/InvenTree/settings/part.html:54 +#: templates/InvenTree/settings/part.html:55 msgid "No part parameter templates found" msgstr "" -#: templates/InvenTree/settings/part.html:74 -msgid "Edit Template" -msgstr "" - -#: templates/InvenTree/settings/part.html:75 -msgid "Delete Template" -msgstr "" - #: templates/InvenTree/settings/po.html:9 msgid "Purchase Order Settings" msgstr "" @@ -4434,6 +4546,10 @@ msgstr "" msgid "Global" msgstr "" +#: templates/InvenTree/settings/tabs.html:19 +msgid "Categories" +msgstr "" + #: templates/InvenTree/settings/theme.html:10 msgid "Theme Settings" msgstr "" @@ -4745,15 +4861,15 @@ msgstr "" msgid "No purchase orders found" msgstr "" -#: templates/js/order.js:180 templates/js/stock.js:677 +#: templates/js/order.js:181 templates/js/stock.js:677 msgid "Date" msgstr "" -#: templates/js/order.js:210 +#: templates/js/order.js:211 msgid "No sales orders found" msgstr "" -#: templates/js/order.js:267 +#: templates/js/order.js:268 msgid "Shipment Date" msgstr "" @@ -5163,38 +5279,38 @@ msgstr "" msgid "Important dates" msgstr "" -#: users/models.py:131 +#: users/models.py:135 msgid "Permission set" msgstr "" -#: users/models.py:139 +#: users/models.py:143 msgid "Group" msgstr "" -#: users/models.py:142 +#: users/models.py:146 msgid "View" msgstr "" -#: users/models.py:142 +#: users/models.py:146 msgid "Permission to view items" msgstr "" -#: users/models.py:144 +#: users/models.py:148 msgid "Add" msgstr "" -#: users/models.py:144 +#: users/models.py:148 msgid "Permission to add items" msgstr "" -#: users/models.py:146 +#: users/models.py:150 msgid "Change" msgstr "" -#: users/models.py:146 +#: users/models.py:150 msgid "Permissions to edit items" msgstr "" -#: users/models.py:148 +#: users/models.py:152 msgid "Permission to delete items" msgstr "" diff --git a/InvenTree/locale/es/LC_MESSAGES/django.po b/InvenTree/locale/es/LC_MESSAGES/django.po index 976e28c2c6..f6cb0f4604 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-11-10 13:31+0000\n" +"POT-Creation-Date: 2020-11-12 22:05+1100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -26,27 +26,31 @@ msgstr "" msgid "No matching action found" msgstr "" -#: InvenTree/forms.py:107 build/forms.py:82 build/forms.py:170 +#: InvenTree/forms.py:108 build/forms.py:82 build/forms.py:170 msgid "Confirm" msgstr "" -#: InvenTree/forms.py:123 +#: InvenTree/forms.py:124 msgid "Confirm item deletion" msgstr "" -#: InvenTree/forms.py:155 +#: InvenTree/forms.py:156 msgid "Enter new password" msgstr "" -#: InvenTree/forms.py:162 +#: InvenTree/forms.py:163 msgid "Confirm new password" msgstr "" -#: InvenTree/forms.py:197 +#: InvenTree/forms.py:198 msgid "Apply Theme" msgstr "" -#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:269 +#: InvenTree/forms.py:228 +msgid "Select Category" +msgstr "" + +#: InvenTree/helpers.py:361 order/models.py:189 order/models.py:271 msgid "Invalid quantity provided" msgstr "" @@ -99,19 +103,19 @@ msgstr "" msgid "Description (optional)" msgstr "" -#: InvenTree/settings.py:350 +#: InvenTree/settings.py:354 msgid "English" msgstr "" -#: InvenTree/settings.py:351 +#: InvenTree/settings.py:355 msgid "German" msgstr "" -#: InvenTree/settings.py:352 +#: InvenTree/settings.py:356 msgid "French" msgstr "" -#: InvenTree/settings.py:353 +#: InvenTree/settings.py:357 msgid "Polish" msgstr "" @@ -172,57 +176,61 @@ msgstr "" msgid "Production" msgstr "" -#: InvenTree/validators.py:39 +#: InvenTree/validators.py:22 +msgid "Not a valid currency code" +msgstr "" + +#: InvenTree/validators.py:50 msgid "Invalid character in part name" msgstr "" -#: InvenTree/validators.py:52 +#: InvenTree/validators.py:63 msgid "IPN must match regex pattern" msgstr "" -#: InvenTree/validators.py:66 InvenTree/validators.py:80 -#: InvenTree/validators.py:94 +#: InvenTree/validators.py:77 InvenTree/validators.py:91 +#: InvenTree/validators.py:105 msgid "Reference must match pattern" msgstr "" -#: InvenTree/validators.py:102 +#: InvenTree/validators.py:113 #, python-brace-format msgid "Illegal character in name ({x})" msgstr "" -#: InvenTree/validators.py:121 InvenTree/validators.py:137 +#: InvenTree/validators.py:132 InvenTree/validators.py:148 msgid "Overage value must not be negative" msgstr "" -#: InvenTree/validators.py:139 +#: InvenTree/validators.py:150 msgid "Overage must not exceed 100%" msgstr "" -#: InvenTree/validators.py:146 +#: InvenTree/validators.py:157 msgid "Overage must be an integer value or a percentage" msgstr "" -#: InvenTree/views.py:493 +#: InvenTree/views.py:494 msgid "Delete Item" msgstr "" -#: InvenTree/views.py:542 +#: InvenTree/views.py:543 msgid "Check box to confirm item deletion" msgstr "" -#: InvenTree/views.py:557 templates/InvenTree/settings/user.html:18 +#: InvenTree/views.py:558 templates/InvenTree/settings/user.html:18 msgid "Edit User Information" msgstr "" -#: InvenTree/views.py:568 templates/InvenTree/settings/user.html:22 +#: InvenTree/views.py:569 templates/InvenTree/settings/user.html:22 msgid "Set Password" msgstr "" -#: InvenTree/views.py:587 +#: InvenTree/views.py:588 msgid "Password fields must match" msgstr "" -#: InvenTree/views.py:757 +#: InvenTree/views.py:794 msgid "Database Statistics" msgstr "" @@ -272,10 +280,10 @@ msgstr "" #: build/forms.py:70 build/templates/build/auto_allocate.html:17 #: build/templates/build/build_base.html:78 -#: build/templates/build/detail.html:29 -#: company/templates/company/supplier_part_pricing.html:75 +#: build/templates/build/detail.html:29 common/models.py:488 +#: company/forms.py:112 company/templates/company/supplier_part_pricing.html:75 #: order/templates/order/order_wizard/select_parts.html:32 -#: order/templates/order/purchase_order_detail.html:178 +#: order/templates/order/purchase_order_detail.html:179 #: order/templates/order/sales_order_detail.html:74 #: order/templates/order/sales_order_detail.html:156 #: part/templates/part/allocation.html:16 @@ -351,7 +359,7 @@ msgstr "" #: build/models.py:57 build/templates/build/index.html:6 #: build/templates/build/index.html:14 order/templates/order/so_builds.html:11 #: order/templates/order/so_tabs.html:9 part/templates/part/tabs.html:31 -#: templates/InvenTree/settings/tabs.html:25 users/models.py:30 +#: templates/InvenTree/settings/tabs.html:28 users/models.py:30 msgid "Build Orders" msgstr "" @@ -359,19 +367,20 @@ msgstr "" msgid "Build Order Reference" msgstr "" -#: build/models.py:73 order/templates/order/purchase_order_detail.html:173 +#: build/models.py:73 order/templates/order/purchase_order_detail.html:174 #: templates/js/bom.js:181 templates/js/build.js:493 msgid "Reference" msgstr "" #: build/models.py:80 build/templates/build/detail.html:19 +#: company/templates/company/detail.html:23 #: company/templates/company/supplier_part_base.html:61 #: company/templates/company/supplier_part_detail.html:27 -#: order/templates/order/purchase_order_detail.html:160 +#: order/templates/order/purchase_order_detail.html:161 #: part/templates/part/detail.html:51 part/templates/part/set_category.html:14 #: templates/InvenTree/search.html:147 templates/js/bom.js:174 #: templates/js/bom.js:499 templates/js/build.js:642 templates/js/company.js:56 -#: templates/js/order.js:167 templates/js/order.js:249 templates/js/part.js:188 +#: templates/js/order.js:168 templates/js/order.js:250 templates/js/part.js:188 #: templates/js/part.js:271 templates/js/part.js:391 templates/js/part.js:572 #: templates/js/stock.js:494 templates/js/stock.js:706 msgid "Description" @@ -392,10 +401,10 @@ msgstr "" #: build/models.py:97 build/templates/build/auto_allocate.html:16 #: build/templates/build/build_base.html:73 -#: build/templates/build/detail.html:24 order/models.py:519 +#: build/templates/build/detail.html:24 order/models.py:530 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:148 -#: order/templates/order/receive_parts.html:19 part/models.py:294 +#: order/templates/order/receive_parts.html:19 part/models.py:315 #: part/templates/part/part_app_base.html:7 part/templates/part/related.html:26 #: part/templates/part/set_category.html:13 templates/InvenTree/search.html:133 #: templates/js/barcode.js:336 templates/js/bom.js:147 templates/js/bom.js:484 @@ -475,13 +484,13 @@ msgstr "" msgid "External Link" msgstr "" -#: build/models.py:177 part/models.py:609 stock/models.py:386 +#: build/models.py:177 part/models.py:672 stock/models.py:386 msgid "Link to external URL" msgstr "" -#: build/models.py:181 build/templates/build/tabs.html:23 company/models.py:314 +#: build/models.py:181 build/templates/build/tabs.html:23 company/models.py:344 #: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:18 -#: order/templates/order/purchase_order_detail.html:203 +#: order/templates/order/purchase_order_detail.html:213 #: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:73 #: stock/forms.py:307 stock/forms.py:339 stock/forms.py:367 stock/models.py:448 #: stock/models.py:1432 stock/templates/stock/tabs.html:26 @@ -528,11 +537,11 @@ msgstr "" msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" msgstr "" -#: build/models.py:908 order/models.py:603 +#: build/models.py:908 order/models.py:614 msgid "StockItem is over-allocated" msgstr "" -#: build/models.py:912 order/models.py:606 +#: build/models.py:912 order/models.py:617 msgid "Allocation quantity must be greater than zero" msgstr "" @@ -676,7 +685,7 @@ msgstr "" #: order/templates/order/receive_parts.html:24 #: stock/templates/stock/item_base.html:312 templates/InvenTree/search.html:175 #: templates/js/barcode.js:42 templates/js/build.js:675 -#: templates/js/order.js:172 templates/js/order.js:254 +#: templates/js/order.js:173 templates/js/order.js:255 #: templates/js/stock.js:557 templates/js/stock.js:961 msgid "Status" msgstr "" @@ -687,13 +696,13 @@ msgid "Progress" msgstr "" #: build/templates/build/build_base.html:101 -#: build/templates/build/detail.html:82 order/models.py:517 +#: build/templates/build/detail.html:82 order/models.py:528 #: order/templates/order/sales_order_base.html:9 #: order/templates/order/sales_order_base.html:33 #: order/templates/order/sales_order_notes.html:10 #: order/templates/order/sales_order_ship.html:25 #: part/templates/part/allocation.html:27 -#: stock/templates/stock/item_base.html:221 templates/js/order.js:221 +#: stock/templates/stock/item_base.html:221 templates/js/order.js:222 msgid "Sales Order" msgstr "" @@ -886,7 +895,7 @@ msgstr "" msgid "Create Build Output" msgstr "" -#: build/views.py:207 stock/models.py:827 stock/views.py:1647 +#: build/views.py:207 stock/models.py:827 stock/views.py:1660 msgid "Serial numbers already exist" msgstr "" @@ -902,7 +911,7 @@ msgstr "" msgid "Confirm unallocation of build stock" msgstr "" -#: build/views.py:303 build/views.py:388 stock/views.py:413 +#: build/views.py:303 build/views.py:388 stock/views.py:417 msgid "Check the confirmation box" msgstr "" @@ -991,8 +1000,8 @@ msgstr "" msgid "Add Build Order Attachment" msgstr "" -#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:166 -#: stock/views.py:176 +#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:168 +#: stock/views.py:180 msgid "Added attachment" msgstr "" @@ -1008,167 +1017,179 @@ msgstr "" msgid "Delete Attachment" msgstr "" -#: build/views.py:1122 order/views.py:240 order/views.py:255 stock/views.py:234 +#: build/views.py:1122 order/views.py:240 order/views.py:255 stock/views.py:238 msgid "Deleted attachment" msgstr "" -#: common/models.py:55 +#: common/models.py:56 msgid "InvenTree Instance Name" msgstr "" -#: common/models.py:57 +#: common/models.py:58 msgid "String descriptor for the server instance" msgstr "" -#: common/models.py:61 company/models.py:89 company/models.py:90 +#: common/models.py:62 company/models.py:95 company/models.py:96 msgid "Company name" msgstr "" -#: common/models.py:62 +#: common/models.py:63 msgid "Internal company name" msgstr "" -#: common/models.py:67 +#: common/models.py:68 msgid "Default Currency" msgstr "" -#: common/models.py:68 +#: common/models.py:69 msgid "Default currency" msgstr "" -#: common/models.py:74 +#: common/models.py:75 msgid "IPN Regex" msgstr "" -#: common/models.py:75 +#: common/models.py:76 msgid "Regular expression pattern for matching Part IPN" msgstr "" -#: common/models.py:79 +#: common/models.py:80 msgid "Allow Duplicate IPN" msgstr "" -#: common/models.py:80 +#: common/models.py:81 msgid "Allow multiple parts to share the same IPN" msgstr "" -#: common/models.py:86 +#: common/models.py:87 msgid "Copy Part BOM Data" msgstr "" -#: common/models.py:87 +#: common/models.py:88 msgid "Copy BOM data by default when duplicating a part" msgstr "" -#: common/models.py:93 +#: common/models.py:94 msgid "Copy Part Parameter Data" msgstr "" -#: common/models.py:94 +#: common/models.py:95 msgid "Copy parameter data by default when duplicating a part" msgstr "" -#: common/models.py:100 +#: common/models.py:101 msgid "Copy Part Test Data" msgstr "" -#: common/models.py:101 +#: common/models.py:102 msgid "Copy test data by default when duplicating a part" msgstr "" -#: common/models.py:107 part/models.py:680 part/templates/part/detail.html:168 +#: common/models.py:108 +msgid "Copy Category Parameter Templates" +msgstr "" + +#: common/models.py:109 +msgid "Copy category parameter templates when creating a part" +msgstr "" + +#: common/models.py:115 part/models.py:743 part/templates/part/detail.html:168 #: templates/js/table_filters.js:264 msgid "Component" msgstr "" -#: common/models.py:108 +#: common/models.py:116 msgid "Parts can be used as sub-components by default" msgstr "" -#: common/models.py:114 part/models.py:691 part/templates/part/detail.html:188 +#: common/models.py:122 part/models.py:754 part/templates/part/detail.html:188 msgid "Purchaseable" msgstr "" -#: common/models.py:115 +#: common/models.py:123 msgid "Parts are purchaseable by default" msgstr "" -#: common/models.py:121 part/models.py:696 part/templates/part/detail.html:198 +#: common/models.py:129 part/models.py:759 part/templates/part/detail.html:198 #: templates/js/table_filters.js:272 msgid "Salable" msgstr "" -#: common/models.py:122 +#: common/models.py:130 msgid "Parts are salable by default" msgstr "" -#: common/models.py:128 part/models.py:686 part/templates/part/detail.html:178 +#: common/models.py:136 part/models.py:749 part/templates/part/detail.html:178 #: templates/js/table_filters.js:31 templates/js/table_filters.js:276 msgid "Trackable" msgstr "" -#: common/models.py:129 +#: common/models.py:137 msgid "Parts are trackable by default" msgstr "" -#: common/models.py:135 +#: common/models.py:143 msgid "Build Order Reference Prefix" msgstr "" -#: common/models.py:136 +#: common/models.py:144 msgid "Prefix value for build order reference" msgstr "" -#: common/models.py:141 +#: common/models.py:149 msgid "Build Order Reference Regex" msgstr "" -#: common/models.py:142 +#: common/models.py:150 msgid "Regular expression pattern for matching build order reference" msgstr "" -#: common/models.py:146 +#: common/models.py:154 msgid "Sales Order Reference Prefix" msgstr "" -#: common/models.py:147 +#: common/models.py:155 msgid "Prefix value for sales order reference" msgstr "" -#: common/models.py:151 +#: common/models.py:159 msgid "Purchase Order Reference Prefix" msgstr "" -#: common/models.py:152 +#: common/models.py:160 msgid "Prefix value for purchase order reference" msgstr "" -#: common/models.py:357 +#: common/models.py:373 msgid "Settings key (must be unique - case insensitive" msgstr "" -#: common/models.py:359 +#: common/models.py:375 msgid "Settings value" msgstr "" -#: common/models.py:415 +#: common/models.py:431 msgid "Value must be a boolean value" msgstr "" -#: common/models.py:429 +#: common/models.py:445 msgid "Key string must be unique" msgstr "" -#: common/models.py:474 company/templates/company/supplier_part_pricing.html:80 +#: common/models.py:489 company/forms.py:113 +msgid "Price break quantity" +msgstr "" + +#: common/models.py:497 company/templates/company/supplier_part_pricing.html:80 #: part/templates/part/sale_prices.html:87 templates/js/bom.js:234 msgid "Price" msgstr "" -#: common/models.py:475 +#: common/models.py:498 msgid "Unit price at specified quantity" msgstr "" -#: common/models.py:498 +#: common/models.py:521 msgid "Default" msgstr "" @@ -1180,109 +1201,125 @@ msgstr "" msgid "Change Setting" msgstr "" -#: company/models.py:92 -msgid "Company description" +#: company/forms.py:37 company/models.py:139 +msgid "Default currency used for this company" msgstr "" -#: company/models.py:92 -msgid "Description of the company" +#: company/forms.py:80 +msgid "Single Price" msgstr "" -#: company/models.py:94 company/templates/company/company_base.html:57 -#: templates/js/company.js:61 -msgid "Website" -msgstr "" - -#: company/models.py:94 -msgid "Company website URL" -msgstr "" - -#: company/models.py:97 company/templates/company/company_base.html:64 -msgid "Address" +#: company/forms.py:82 +msgid "Single quantity price" msgstr "" #: company/models.py:98 +msgid "Company description" +msgstr "" + +#: company/models.py:98 +msgid "Description of the company" +msgstr "" + +#: company/models.py:100 company/templates/company/company_base.html:57 +#: company/templates/company/detail.html:28 templates/js/company.js:61 +msgid "Website" +msgstr "" + +#: company/models.py:100 +msgid "Company website URL" +msgstr "" + +#: company/models.py:103 company/templates/company/company_base.html:64 +msgid "Address" +msgstr "" + +#: company/models.py:104 msgid "Company address" msgstr "" -#: company/models.py:101 +#: company/models.py:107 msgid "Phone number" msgstr "" -#: company/models.py:102 +#: company/models.py:108 msgid "Contact phone number" msgstr "" -#: company/models.py:105 company/templates/company/company_base.html:78 +#: company/models.py:111 company/templates/company/company_base.html:78 msgid "Email" msgstr "" -#: company/models.py:105 +#: company/models.py:111 msgid "Contact email address" msgstr "" -#: company/models.py:108 company/templates/company/company_base.html:85 +#: company/models.py:114 company/templates/company/company_base.html:85 msgid "Contact" msgstr "" -#: company/models.py:109 +#: company/models.py:115 msgid "Point of contact" msgstr "" -#: company/models.py:111 +#: company/models.py:117 msgid "Link to external company information" msgstr "" -#: company/models.py:123 +#: company/models.py:129 msgid "Do you sell items to this company?" msgstr "" -#: company/models.py:125 +#: company/models.py:131 msgid "Do you purchase items from this company?" msgstr "" -#: company/models.py:127 +#: company/models.py:133 msgid "Does this company manufacture parts?" msgstr "" -#: company/models.py:283 stock/models.py:338 +#: company/models.py:137 company/templates/company/detail.html:37 +msgid "Currency" +msgstr "" + +#: company/models.py:313 stock/models.py:338 #: stock/templates/stock/item_base.html:177 msgid "Base Part" msgstr "" -#: company/models.py:288 +#: company/models.py:318 msgid "Select part" msgstr "" -#: company/models.py:294 +#: company/models.py:324 msgid "Select supplier" msgstr "" -#: company/models.py:297 +#: company/models.py:327 msgid "Supplier stock keeping unit" msgstr "" -#: company/models.py:304 +#: company/models.py:334 msgid "Select manufacturer" msgstr "" -#: company/models.py:308 +#: company/models.py:338 msgid "Manufacturer part number" msgstr "" -#: company/models.py:310 +#: company/models.py:340 msgid "URL for external supplier part link" msgstr "" -#: company/models.py:312 +#: company/models.py:342 msgid "Supplier part description" msgstr "" -#: company/models.py:316 +#: company/models.py:346 msgid "Minimum charge (e.g. stocking fee)" msgstr "" -#: company/models.py:318 +#: company/models.py:348 msgid "Part packaging" msgstr "" @@ -1305,27 +1342,39 @@ msgstr "" msgid "Phone" msgstr "" -#: company/templates/company/detail.html:16 +#: company/templates/company/detail.html:18 +msgid "Company Name" +msgstr "" + +#: company/templates/company/detail.html:31 +msgid "No website specified" +msgstr "" + +#: company/templates/company/detail.html:40 +msgid "Uses default currency" +msgstr "" + +#: company/templates/company/detail.html:52 #: company/templates/company/supplier_part_base.html:84 #: company/templates/company/supplier_part_detail.html:30 part/bom.py:172 #: templates/js/company.js:44 templates/js/company.js:188 msgid "Manufacturer" msgstr "" -#: company/templates/company/detail.html:21 +#: company/templates/company/detail.html:57 #: company/templates/company/supplier_part_base.html:74 #: company/templates/company/supplier_part_detail.html:21 #: order/templates/order/order_base.html:79 #: order/templates/order/order_wizard/select_pos.html:30 part/bom.py:170 #: stock/templates/stock/item_base.html:287 templates/js/company.js:48 -#: templates/js/company.js:164 templates/js/order.js:154 +#: templates/js/company.js:164 templates/js/order.js:155 msgid "Supplier" msgstr "" -#: company/templates/company/detail.html:26 +#: company/templates/company/detail.html:62 #: order/templates/order/sales_order_base.html:81 stock/models.py:373 #: stock/models.py:374 stock/templates/stock/item_base.html:204 -#: templates/js/company.js:40 templates/js/order.js:236 +#: templates/js/company.js:40 templates/js/order.js:237 msgid "Customer" msgstr "" @@ -1372,21 +1421,21 @@ msgstr "" msgid "Create new Part" msgstr "" -#: company/templates/company/detail_part.html:69 company/views.py:55 +#: company/templates/company/detail_part.html:69 company/views.py:56 #: part/templates/part/supplier.html:47 msgid "New Supplier" msgstr "" -#: company/templates/company/detail_part.html:70 company/views.py:194 +#: company/templates/company/detail_part.html:70 company/views.py:195 msgid "Create new Supplier" msgstr "" -#: company/templates/company/detail_part.html:75 company/views.py:62 +#: company/templates/company/detail_part.html:75 company/views.py:63 #: part/templates/part/supplier.html:53 msgid "New Manufacturer" msgstr "" -#: company/templates/company/detail_part.html:76 company/views.py:197 +#: company/templates/company/detail_part.html:76 company/views.py:198 msgid "Create new Manufacturer" msgstr "" @@ -1420,7 +1469,7 @@ msgstr "" #: order/templates/order/purchase_orders.html:7 #: order/templates/order/purchase_orders.html:12 #: part/templates/part/orders.html:9 part/templates/part/tabs.html:48 -#: templates/InvenTree/settings/tabs.html:28 templates/navbar.html:33 +#: templates/InvenTree/settings/tabs.html:31 templates/navbar.html:33 #: users/models.py:31 msgid "Purchase Orders" msgstr "" @@ -1440,7 +1489,7 @@ msgstr "" #: order/templates/order/sales_orders.html:7 #: order/templates/order/sales_orders.html:12 #: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:56 -#: templates/InvenTree/settings/tabs.html:31 templates/navbar.html:42 +#: templates/InvenTree/settings/tabs.html:34 templates/navbar.html:42 #: users/models.py:32 msgid "Sales Orders" msgstr "" @@ -1513,8 +1562,8 @@ msgstr "" msgid "Pricing Information" msgstr "" -#: company/templates/company/supplier_part_pricing.html:17 company/views.py:412 -#: part/templates/part/sale_prices.html:14 part/views.py:2350 +#: company/templates/company/supplier_part_pricing.html:17 company/views.py:459 +#: part/templates/part/sale_prices.html:14 part/views.py:2546 msgid "Add Price Break" msgstr "" @@ -1544,7 +1593,7 @@ msgstr "" #: company/templates/company/supplier_part_tabs.html:8 #: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18 #: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155 -#: templates/InvenTree/settings/tabs.html:22 templates/js/part.js:192 +#: templates/InvenTree/settings/tabs.html:25 templates/js/part.js:192 #: templates/js/part.js:418 templates/js/stock.js:502 templates/navbar.html:22 #: users/models.py:29 msgid "Stock" @@ -1555,96 +1604,96 @@ msgid "Orders" msgstr "" #: company/templates/company/tabs.html:9 -#: order/templates/order/receive_parts.html:14 part/models.py:295 +#: order/templates/order/receive_parts.html:14 part/models.py:316 #: part/templates/part/cat_link.html:7 part/templates/part/category.html:94 #: part/templates/part/category_tabs.html:6 -#: templates/InvenTree/settings/tabs.html:19 templates/navbar.html:19 +#: templates/InvenTree/settings/tabs.html:22 templates/navbar.html:19 #: templates/stats.html:8 templates/stats.html:17 users/models.py:28 msgid "Parts" msgstr "" -#: company/views.py:54 part/templates/part/tabs.html:42 +#: company/views.py:55 part/templates/part/tabs.html:42 #: templates/navbar.html:31 msgid "Suppliers" msgstr "" -#: company/views.py:61 templates/navbar.html:32 +#: company/views.py:62 templates/navbar.html:32 msgid "Manufacturers" msgstr "" -#: company/views.py:68 templates/navbar.html:41 +#: company/views.py:69 templates/navbar.html:41 msgid "Customers" msgstr "" -#: company/views.py:69 +#: company/views.py:70 msgid "New Customer" msgstr "" -#: company/views.py:77 +#: company/views.py:78 msgid "Companies" msgstr "" -#: company/views.py:78 +#: company/views.py:79 msgid "New Company" msgstr "" -#: company/views.py:156 +#: company/views.py:157 msgid "Update Company Image" msgstr "" -#: company/views.py:162 +#: company/views.py:163 msgid "Updated company image" msgstr "" -#: company/views.py:172 +#: company/views.py:173 msgid "Edit Company" msgstr "" -#: company/views.py:177 +#: company/views.py:178 msgid "Edited company information" msgstr "" -#: company/views.py:200 +#: company/views.py:201 msgid "Create new Customer" msgstr "" -#: company/views.py:202 +#: company/views.py:203 msgid "Create new Company" msgstr "" -#: company/views.py:229 +#: company/views.py:230 msgid "Created new company" msgstr "" -#: company/views.py:239 +#: company/views.py:240 msgid "Delete Company" msgstr "" -#: company/views.py:245 +#: company/views.py:246 msgid "Company was deleted" msgstr "" -#: company/views.py:270 +#: company/views.py:271 msgid "Edit Supplier Part" msgstr "" -#: company/views.py:280 templates/js/stock.js:846 +#: company/views.py:289 templates/js/stock.js:846 msgid "Create new Supplier Part" msgstr "" -#: company/views.py:341 +#: company/views.py:388 msgid "Delete Supplier Part" msgstr "" -#: company/views.py:418 part/views.py:2356 +#: company/views.py:465 part/views.py:2552 msgid "Added new price break" msgstr "" -#: company/views.py:454 part/views.py:2400 +#: company/views.py:521 part/views.py:2596 msgid "Edit Price Break" msgstr "" -#: company/views.py:470 part/views.py:2416 +#: company/views.py:537 part/views.py:2612 msgid "Delete Price Break" msgstr "" @@ -1701,114 +1750,123 @@ msgstr "" msgid "Enter sales order number" msgstr "" -#: order/models.py:108 +#: order/models.py:110 msgid "Order reference" msgstr "" -#: order/models.py:110 +#: order/models.py:112 msgid "Order description" msgstr "" -#: order/models.py:112 +#: order/models.py:114 msgid "Link to external page" msgstr "" -#: order/models.py:122 +#: order/models.py:124 msgid "Order notes" msgstr "" -#: order/models.py:140 order/models.py:326 +#: order/models.py:142 order/models.py:328 msgid "Purchase order status" msgstr "" -#: order/models.py:148 +#: order/models.py:150 msgid "Company from which the items are being ordered" msgstr "" -#: order/models.py:151 +#: order/models.py:153 msgid "Supplier order reference code" msgstr "" -#: order/models.py:160 +#: order/models.py:162 msgid "Date order was issued" msgstr "" -#: order/models.py:162 +#: order/models.py:164 msgid "Date order was completed" msgstr "" -#: order/models.py:185 order/models.py:267 part/views.py:1479 +#: order/models.py:187 order/models.py:269 part/views.py:1496 #: stock/models.py:244 stock/models.py:811 msgid "Quantity must be greater than zero" msgstr "" -#: order/models.py:190 +#: order/models.py:192 msgid "Part supplier must match PO supplier" msgstr "" -#: order/models.py:262 +#: order/models.py:264 msgid "Lines can only be received against an order marked as 'Placed'" msgstr "" -#: order/models.py:322 +#: order/models.py:324 msgid "Company to which the items are being sold" msgstr "" -#: order/models.py:328 +#: order/models.py:330 msgid "Customer order reference code" msgstr "" -#: order/models.py:367 +#: order/models.py:369 msgid "SalesOrder cannot be shipped as it is not currently pending" msgstr "" -#: order/models.py:454 +#: order/models.py:456 msgid "Item quantity" msgstr "" -#: order/models.py:456 +#: order/models.py:458 msgid "Line item reference" msgstr "" -#: order/models.py:458 +#: order/models.py:460 msgid "Line item notes" msgstr "" -#: order/models.py:484 order/templates/order/order_base.html:9 +#: order/models.py:486 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 #: stock/templates/stock/item_base.html:259 templates/js/order.js:139 msgid "Purchase Order" msgstr "" -#: order/models.py:497 +#: order/models.py:499 msgid "Supplier part" msgstr "" -#: order/models.py:500 +#: order/models.py:502 msgid "Number of items received" msgstr "" -#: order/models.py:594 +#: order/models.py:509 stock/models.py:457 +#: stock/templates/stock/item_base.html:266 +msgid "Purchase Price" +msgstr "" + +#: order/models.py:510 +msgid "Unit purchase price" +msgstr "" + +#: order/models.py:605 msgid "Cannot allocate stock item to a line with a different part" msgstr "" -#: order/models.py:596 +#: order/models.py:607 msgid "Cannot allocate stock to a line without a part" msgstr "" -#: order/models.py:599 +#: order/models.py:610 msgid "Allocation quantity cannot exceed stock quantity" msgstr "" -#: order/models.py:609 +#: order/models.py:620 msgid "Quantity must be 1 for serialized stock item" msgstr "" -#: order/models.py:625 +#: order/models.py:636 msgid "Select stock item to allocate" msgstr "" -#: order/models.py:628 +#: order/models.py:639 msgid "Enter stock allocation quantity" msgstr "" @@ -1844,7 +1902,7 @@ msgstr "" msgid "Order Status" msgstr "" -#: order/templates/order/order_base.html:85 templates/js/order.js:161 +#: order/templates/order/order_base.html:85 templates/js/order.js:162 msgid "Supplier Reference" msgstr "" @@ -1853,7 +1911,7 @@ msgid "Issued" msgstr "" #: order/templates/order/order_base.html:111 -#: order/templates/order/purchase_order_detail.html:183 +#: order/templates/order/purchase_order_detail.html:193 #: order/templates/order/receive_parts.html:22 #: order/templates/order/sales_order_base.html:113 msgid "Received" @@ -1900,7 +1958,7 @@ msgid "Select existing purchase orders, or create new orders." msgstr "" #: order/templates/order/order_wizard/select_pos.html:31 -#: templates/js/order.js:185 templates/js/order.js:272 +#: templates/js/order.js:186 templates/js/order.js:273 msgid "Items" msgstr "" @@ -1952,21 +2010,25 @@ msgstr "" msgid "No line items found" msgstr "" -#: order/templates/order/purchase_order_detail.html:165 +#: order/templates/order/purchase_order_detail.html:166 #: order/templates/order/receive_parts.html:20 msgid "Order Code" msgstr "" -#: order/templates/order/purchase_order_detail.html:214 +#: order/templates/order/purchase_order_detail.html:184 +msgid "Unit Price" +msgstr "" + +#: order/templates/order/purchase_order_detail.html:225 #: order/templates/order/sales_order_detail.html:285 msgid "Edit line item" msgstr "" -#: order/templates/order/purchase_order_detail.html:215 +#: order/templates/order/purchase_order_detail.html:226 msgid "Delete line item" msgstr "" -#: order/templates/order/purchase_order_detail.html:220 +#: order/templates/order/purchase_order_detail.html:231 msgid "Receive line item" msgstr "" @@ -2003,7 +2065,7 @@ msgstr "" msgid "Sales Order Details" msgstr "" -#: order/templates/order/sales_order_base.html:87 templates/js/order.js:243 +#: order/templates/order/sales_order_base.html:87 templates/js/order.js:244 msgid "Customer Reference" msgstr "" @@ -2256,367 +2318,389 @@ msgstr "" msgid "Error reading BOM file (incorrect row size)" msgstr "" -#: part/forms.py:60 stock/forms.py:255 +#: part/forms.py:61 stock/forms.py:255 msgid "File Format" msgstr "" -#: part/forms.py:60 stock/forms.py:255 +#: part/forms.py:61 stock/forms.py:255 msgid "Select output file format" msgstr "" -#: part/forms.py:62 +#: part/forms.py:63 msgid "Cascading" msgstr "" -#: part/forms.py:62 +#: part/forms.py:63 msgid "Download cascading / multi-level BOM" msgstr "" -#: part/forms.py:64 +#: part/forms.py:65 msgid "Levels" msgstr "" -#: part/forms.py:64 +#: part/forms.py:65 msgid "Select maximum number of BOM levels to export (0 = all levels)" msgstr "" -#: part/forms.py:66 +#: part/forms.py:67 msgid "Include Parameter Data" msgstr "" -#: part/forms.py:66 +#: part/forms.py:67 msgid "Include part parameters data in exported BOM" msgstr "" -#: part/forms.py:68 +#: part/forms.py:69 msgid "Include Stock Data" msgstr "" -#: part/forms.py:68 +#: part/forms.py:69 msgid "Include part stock data in exported BOM" msgstr "" -#: part/forms.py:70 +#: part/forms.py:71 msgid "Include Supplier Data" msgstr "" -#: part/forms.py:70 +#: part/forms.py:71 msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:91 part/models.py:1644 +#: part/forms.py:92 part/models.py:1715 msgid "Parent Part" msgstr "" -#: part/forms.py:92 part/templates/part/bom_duplicate.html:7 +#: part/forms.py:93 part/templates/part/bom_duplicate.html:7 msgid "Select parent part to copy BOM from" msgstr "" -#: part/forms.py:98 +#: part/forms.py:99 msgid "Clear existing BOM items" msgstr "" -#: part/forms.py:103 +#: part/forms.py:104 msgid "Confirm BOM duplication" msgstr "" -#: part/forms.py:121 +#: part/forms.py:122 msgid "Confirm that the BOM is correct" msgstr "" -#: part/forms.py:133 +#: part/forms.py:134 msgid "Select BOM file to upload" msgstr "" -#: part/forms.py:152 +#: part/forms.py:153 msgid "Related Part" msgstr "" -#: part/forms.py:171 +#: part/forms.py:172 msgid "Select part category" msgstr "" -#: part/forms.py:187 +#: part/forms.py:188 msgid "Duplicate all BOM data for this part" msgstr "" -#: part/forms.py:188 +#: part/forms.py:189 msgid "Copy BOM" msgstr "" -#: part/forms.py:193 +#: part/forms.py:194 msgid "Duplicate all parameter data for this part" msgstr "" -#: part/forms.py:194 +#: part/forms.py:195 msgid "Copy Parameters" msgstr "" -#: part/forms.py:199 +#: part/forms.py:200 msgid "Confirm part creation" msgstr "" -#: part/forms.py:296 +#: part/forms.py:205 +msgid "Include category parameter templates" +msgstr "" + +#: part/forms.py:210 +msgid "Include parent categories parameter templates" +msgstr "" + +#: part/forms.py:285 +msgid "Add parameter template to same level categories" +msgstr "" + +#: part/forms.py:289 +msgid "Add parameter template to all categories" +msgstr "" + +#: part/forms.py:331 msgid "Input quantity for price calculation" msgstr "" -#: part/models.py:67 +#: part/models.py:68 msgid "Default location for parts in this category" msgstr "" -#: part/models.py:70 +#: part/models.py:71 msgid "Default keywords for parts in this category" msgstr "" -#: part/models.py:76 part/templates/part/part_app_base.html:9 +#: part/models.py:77 part/models.py:1760 +#: part/templates/part/part_app_base.html:9 msgid "Part Category" msgstr "" -#: part/models.py:77 part/templates/part/category.html:18 +#: part/models.py:78 part/templates/part/category.html:18 #: part/templates/part/category.html:89 templates/stats.html:12 msgid "Part Categories" msgstr "" -#: part/models.py:346 part/models.py:356 +#: part/models.py:408 part/models.py:418 #, python-brace-format msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" msgstr "" -#: part/models.py:453 +#: part/models.py:515 msgid "Next available serial numbers are" msgstr "" -#: part/models.py:457 +#: part/models.py:519 msgid "Next available serial number is" msgstr "" -#: part/models.py:462 +#: part/models.py:524 msgid "Most recent serial number is" msgstr "" -#: part/models.py:541 +#: part/models.py:603 msgid "Duplicate IPN not allowed in part settings" msgstr "" -#: part/models.py:552 +#: part/models.py:614 msgid "Part must be unique for name, IPN and revision" msgstr "" -#: part/models.py:581 part/templates/part/detail.html:19 +#: part/models.py:644 part/templates/part/detail.html:19 msgid "Part name" msgstr "" -#: part/models.py:585 +#: part/models.py:648 msgid "Is this part a template part?" msgstr "" -#: part/models.py:594 +#: part/models.py:657 msgid "Is this part a variant of another part?" msgstr "" -#: part/models.py:596 +#: part/models.py:659 msgid "Part description" msgstr "" -#: part/models.py:598 +#: part/models.py:661 msgid "Part keywords to improve visibility in search results" msgstr "" -#: part/models.py:603 +#: part/models.py:666 msgid "Part category" msgstr "" -#: part/models.py:605 +#: part/models.py:668 msgid "Internal Part Number" msgstr "" -#: part/models.py:607 +#: part/models.py:670 msgid "Part revision or version number" msgstr "" -#: part/models.py:621 +#: part/models.py:684 msgid "Where is this item normally stored?" msgstr "" -#: part/models.py:665 +#: part/models.py:728 msgid "Default supplier part" msgstr "" -#: part/models.py:668 +#: part/models.py:731 msgid "Minimum allowed stock level" msgstr "" -#: part/models.py:670 +#: part/models.py:733 msgid "Stock keeping units for this part" msgstr "" -#: part/models.py:674 part/templates/part/detail.html:158 +#: part/models.py:737 part/templates/part/detail.html:158 #: templates/js/table_filters.js:260 msgid "Assembly" msgstr "" -#: part/models.py:675 +#: part/models.py:738 msgid "Can this part be built from other parts?" msgstr "" -#: part/models.py:681 +#: part/models.py:744 msgid "Can this part be used to build other parts?" msgstr "" -#: part/models.py:687 +#: part/models.py:750 msgid "Does this part have tracking for unique items?" msgstr "" -#: part/models.py:692 +#: part/models.py:755 msgid "Can this part be purchased from external suppliers?" msgstr "" -#: part/models.py:697 +#: part/models.py:760 msgid "Can this part be sold to customers?" msgstr "" -#: part/models.py:701 part/templates/part/detail.html:215 +#: part/models.py:764 part/templates/part/detail.html:215 #: templates/js/table_filters.js:19 templates/js/table_filters.js:55 #: templates/js/table_filters.js:186 templates/js/table_filters.js:243 msgid "Active" msgstr "" -#: part/models.py:702 +#: part/models.py:765 msgid "Is this part active?" msgstr "" -#: part/models.py:706 part/templates/part/detail.html:138 +#: part/models.py:769 part/templates/part/detail.html:138 #: templates/js/table_filters.js:27 msgid "Virtual" msgstr "" -#: part/models.py:707 +#: part/models.py:770 msgid "Is this a virtual part, such as a software product or license?" msgstr "" -#: part/models.py:709 +#: part/models.py:772 msgid "Part notes - supports Markdown formatting" msgstr "" -#: part/models.py:711 +#: part/models.py:774 msgid "Stored BOM checksum" msgstr "" -#: part/models.py:1517 +#: part/models.py:1588 msgid "Test templates can only be created for trackable parts" msgstr "" -#: part/models.py:1534 +#: part/models.py:1605 msgid "Test with this name already exists for this part" msgstr "" -#: part/models.py:1553 templates/js/part.js:567 templates/js/stock.js:92 +#: part/models.py:1624 templates/js/part.js:567 templates/js/stock.js:92 msgid "Test Name" msgstr "" -#: part/models.py:1554 +#: part/models.py:1625 msgid "Enter a name for the test" msgstr "" -#: part/models.py:1559 +#: part/models.py:1630 msgid "Test Description" msgstr "" -#: part/models.py:1560 +#: part/models.py:1631 msgid "Enter description for this test" msgstr "" -#: part/models.py:1565 templates/js/part.js:576 +#: part/models.py:1636 templates/js/part.js:576 #: templates/js/table_filters.js:172 msgid "Required" msgstr "" -#: part/models.py:1566 +#: part/models.py:1637 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1571 templates/js/part.js:584 +#: part/models.py:1642 templates/js/part.js:584 msgid "Requires Value" msgstr "" -#: part/models.py:1572 +#: part/models.py:1643 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1577 templates/js/part.js:591 +#: part/models.py:1648 templates/js/part.js:591 msgid "Requires Attachment" msgstr "" -#: part/models.py:1578 +#: part/models.py:1649 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1611 +#: part/models.py:1682 msgid "Parameter template name must be unique" msgstr "" -#: part/models.py:1616 +#: part/models.py:1687 msgid "Parameter Name" msgstr "" -#: part/models.py:1618 +#: part/models.py:1689 msgid "Parameter Units" msgstr "" -#: part/models.py:1646 +#: part/models.py:1717 part/models.py:1765 +#: templates/InvenTree/settings/category.html:62 msgid "Parameter Template" msgstr "" -#: part/models.py:1648 +#: part/models.py:1719 msgid "Parameter Value" msgstr "" -#: part/models.py:1685 +#: part/models.py:1769 +msgid "Default Parameter Value" +msgstr "" + +#: part/models.py:1799 msgid "Select parent part" msgstr "" -#: part/models.py:1693 +#: part/models.py:1807 msgid "Select part to be used in BOM" msgstr "" -#: part/models.py:1699 +#: part/models.py:1813 msgid "BOM quantity for this BOM item" msgstr "" -#: part/models.py:1701 +#: part/models.py:1815 msgid "This BOM item is optional" msgstr "" -#: part/models.py:1704 +#: part/models.py:1818 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "" -#: part/models.py:1707 +#: part/models.py:1821 msgid "BOM item reference" msgstr "" -#: part/models.py:1710 +#: part/models.py:1824 msgid "BOM item notes" msgstr "" -#: part/models.py:1712 +#: part/models.py:1826 msgid "BOM line checksum" msgstr "" -#: part/models.py:1779 part/views.py:1485 part/views.py:1537 +#: part/models.py:1893 part/views.py:1502 part/views.py:1554 #: stock/models.py:234 msgid "Quantity must be integer value for trackable parts" msgstr "" -#: part/models.py:1795 +#: part/models.py:1909 msgid "BOM Item" msgstr "" -#: part/models.py:1910 +#: part/models.py:2024 msgid "Select Related Part" msgstr "" -#: part/models.py:1942 +#: part/models.py:2056 msgid "" "Error creating relationship: check that the part is not related to itself " "and that the relationship is unique" @@ -2704,7 +2788,7 @@ msgstr "" msgid "Validate" msgstr "" -#: part/templates/part/bom.html:62 part/views.py:1776 +#: part/templates/part/bom.html:62 part/views.py:1793 msgid "Export Bill of Materials" msgstr "" @@ -2800,7 +2884,7 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:24 part/views.py:2167 +#: part/templates/part/category.html:24 part/views.py:2184 msgid "Create new part category" msgstr "" @@ -2872,7 +2956,7 @@ msgstr "" msgid "Create new Part Category" msgstr "" -#: part/templates/part/category.html:216 stock/views.py:1338 +#: part/templates/part/category.html:216 stock/views.py:1342 msgid "Create new Stock Location" msgstr "" @@ -2934,7 +3018,7 @@ msgstr "" msgid "Minimum Stock" msgstr "" -#: part/templates/part/detail.html:114 templates/js/order.js:262 +#: part/templates/part/detail.html:114 templates/js/order.js:263 msgid "Creation Date" msgstr "" @@ -3023,7 +3107,9 @@ msgstr "" msgid "Add new parameter" msgstr "" -#: part/templates/part/params.html:15 templates/InvenTree/settings/part.html:37 +#: part/templates/part/params.html:15 +#: templates/InvenTree/settings/category.html:29 +#: templates/InvenTree/settings/part.html:38 msgid "New Parameter" msgstr "" @@ -3033,7 +3119,7 @@ msgid "Value" msgstr "" #: part/templates/part/params.html:41 part/templates/part/related.html:41 -#: part/templates/part/supplier.html:19 users/models.py:148 +#: part/templates/part/supplier.html:19 users/models.py:152 msgid "Delete" msgstr "" @@ -3241,208 +3327,220 @@ msgstr "" msgid "New Variant" msgstr "" -#: part/views.py:82 +#: part/views.py:84 msgid "Add Related Part" msgstr "" -#: part/views.py:138 +#: part/views.py:140 msgid "Delete Related Part" msgstr "" -#: part/views.py:150 +#: part/views.py:152 msgid "Add part attachment" msgstr "" -#: part/views.py:205 templates/attachment_table.html:34 +#: part/views.py:207 templates/attachment_table.html:34 msgid "Edit attachment" msgstr "" -#: part/views.py:211 +#: part/views.py:213 msgid "Part attachment updated" msgstr "" -#: part/views.py:226 +#: part/views.py:228 msgid "Delete Part Attachment" msgstr "" -#: part/views.py:234 +#: part/views.py:236 msgid "Deleted part attachment" msgstr "" -#: part/views.py:243 +#: part/views.py:245 msgid "Create Test Template" msgstr "" -#: part/views.py:272 +#: part/views.py:274 msgid "Edit Test Template" msgstr "" -#: part/views.py:288 +#: part/views.py:290 msgid "Delete Test Template" msgstr "" -#: part/views.py:297 +#: part/views.py:299 msgid "Set Part Category" msgstr "" -#: part/views.py:347 +#: part/views.py:349 #, python-brace-format msgid "Set category for {n} parts" msgstr "" -#: part/views.py:382 +#: part/views.py:384 msgid "Create Variant" msgstr "" -#: part/views.py:464 +#: part/views.py:466 msgid "Duplicate Part" msgstr "" -#: part/views.py:471 +#: part/views.py:473 msgid "Copied part" msgstr "" -#: part/views.py:525 part/views.py:655 +#: part/views.py:527 part/views.py:661 msgid "Possible matches exist - confirm creation of new part" msgstr "" -#: part/views.py:590 templates/js/stock.js:840 +#: part/views.py:592 templates/js/stock.js:840 msgid "Create New Part" msgstr "" -#: part/views.py:597 +#: part/views.py:599 msgid "Created new part" msgstr "" -#: part/views.py:813 +#: part/views.py:830 msgid "Part QR Code" msgstr "" -#: part/views.py:832 +#: part/views.py:849 msgid "Upload Part Image" msgstr "" -#: part/views.py:840 part/views.py:877 +#: part/views.py:857 part/views.py:894 msgid "Updated part image" msgstr "" -#: part/views.py:849 +#: part/views.py:866 msgid "Select Part Image" msgstr "" -#: part/views.py:880 +#: part/views.py:897 msgid "Part image not found" msgstr "" -#: part/views.py:891 +#: part/views.py:908 msgid "Edit Part Properties" msgstr "" -#: part/views.py:918 +#: part/views.py:935 msgid "Duplicate BOM" msgstr "" -#: part/views.py:949 +#: part/views.py:966 msgid "Confirm duplication of BOM from parent" msgstr "" -#: part/views.py:970 +#: part/views.py:987 msgid "Validate BOM" msgstr "" -#: part/views.py:993 +#: part/views.py:1010 msgid "Confirm that the BOM is valid" msgstr "" -#: part/views.py:1004 +#: part/views.py:1021 msgid "Validated Bill of Materials" msgstr "" -#: part/views.py:1138 +#: part/views.py:1155 msgid "No BOM file provided" msgstr "" -#: part/views.py:1488 +#: part/views.py:1505 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1513 part/views.py:1516 +#: part/views.py:1530 part/views.py:1533 msgid "Select valid part" msgstr "" -#: part/views.py:1522 +#: part/views.py:1539 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1560 +#: part/views.py:1577 msgid "Select a part" msgstr "" -#: part/views.py:1566 +#: part/views.py:1583 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1570 +#: part/views.py:1587 msgid "Specify quantity" msgstr "" -#: part/views.py:1826 +#: part/views.py:1843 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1835 +#: part/views.py:1852 msgid "Part was deleted" msgstr "" -#: part/views.py:1844 +#: part/views.py:1861 msgid "Part Pricing" msgstr "" -#: part/views.py:1958 +#: part/views.py:1975 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1968 +#: part/views.py:1985 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1977 +#: part/views.py:1994 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1987 +#: part/views.py:2004 msgid "Create Part Parameter" msgstr "" -#: part/views.py:2039 +#: part/views.py:2056 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:2055 +#: part/views.py:2072 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:2114 +#: part/views.py:2131 msgid "Edit Part Category" msgstr "" -#: part/views.py:2151 +#: part/views.py:2168 msgid "Delete Part Category" msgstr "" -#: part/views.py:2159 +#: part/views.py:2176 msgid "Part category was deleted" msgstr "" -#: part/views.py:2222 +#: part/views.py:2232 +msgid "Create Category Parameter Template" +msgstr "" + +#: part/views.py:2335 +msgid "Edit Category Parameter Template" +msgstr "" + +#: part/views.py:2393 +msgid "Delete Category Parameter Template" +msgstr "" + +#: part/views.py:2418 msgid "Create BOM Item" msgstr "" -#: part/views.py:2290 +#: part/views.py:2486 msgid "Edit BOM item" msgstr "" -#: part/views.py:2340 +#: part/views.py:2536 msgid "Confim BOM item deletion" msgstr "" @@ -3526,7 +3624,7 @@ msgstr "" msgid "Add note (required)" msgstr "" -#: stock/forms.py:371 stock/views.py:916 stock/views.py:1114 +#: stock/forms.py:371 stock/views.py:920 stock/views.py:1118 msgid "Confirm stock adjustment" msgstr "" @@ -3644,10 +3742,6 @@ msgstr "" msgid "Stock Item Notes" msgstr "" -#: stock/models.py:457 stock/templates/stock/item_base.html:266 -msgid "Purchase Price" -msgstr "" - #: stock/models.py:458 msgid "Single unit purchase price at time of purchase" msgstr "" @@ -4041,7 +4135,7 @@ msgstr "" msgid "The following stock items will be uninstalled" msgstr "" -#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1310 +#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1314 msgid "Convert Stock Item" msgstr "" @@ -4073,222 +4167,222 @@ msgstr "" msgid "Installed Items" msgstr "" -#: stock/views.py:119 +#: stock/views.py:123 msgid "Edit Stock Location" msgstr "" -#: stock/views.py:144 +#: stock/views.py:148 msgid "Stock Location QR code" msgstr "" -#: stock/views.py:163 +#: stock/views.py:167 msgid "Add Stock Item Attachment" msgstr "" -#: stock/views.py:210 +#: stock/views.py:214 msgid "Edit Stock Item Attachment" msgstr "" -#: stock/views.py:227 +#: stock/views.py:231 msgid "Delete Stock Item Attachment" msgstr "" -#: stock/views.py:244 +#: stock/views.py:248 msgid "Assign to Customer" msgstr "" -#: stock/views.py:254 +#: stock/views.py:258 msgid "Customer must be specified" msgstr "" -#: stock/views.py:278 +#: stock/views.py:282 msgid "Return to Stock" msgstr "" -#: stock/views.py:288 +#: stock/views.py:292 msgid "Specify a valid location" msgstr "" -#: stock/views.py:299 +#: stock/views.py:303 msgid "Stock item returned from customer" msgstr "" -#: stock/views.py:309 +#: stock/views.py:313 msgid "Select Label Template" msgstr "" -#: stock/views.py:332 +#: stock/views.py:336 msgid "Select valid label" msgstr "" -#: stock/views.py:396 +#: stock/views.py:400 msgid "Delete All Test Data" msgstr "" -#: stock/views.py:412 +#: stock/views.py:416 msgid "Confirm test data deletion" msgstr "" -#: stock/views.py:432 +#: stock/views.py:436 msgid "Add Test Result" msgstr "" -#: stock/views.py:473 +#: stock/views.py:477 msgid "Edit Test Result" msgstr "" -#: stock/views.py:491 +#: stock/views.py:495 msgid "Delete Test Result" msgstr "" -#: stock/views.py:503 +#: stock/views.py:507 msgid "Select Test Report Template" msgstr "" -#: stock/views.py:518 +#: stock/views.py:522 msgid "Select valid template" msgstr "" -#: stock/views.py:571 +#: stock/views.py:575 msgid "Stock Export Options" msgstr "" -#: stock/views.py:693 +#: stock/views.py:697 msgid "Stock Item QR Code" msgstr "" -#: stock/views.py:719 +#: stock/views.py:723 msgid "Install Stock Item" msgstr "" -#: stock/views.py:819 +#: stock/views.py:823 msgid "Uninstall Stock Items" msgstr "" -#: stock/views.py:927 +#: stock/views.py:931 msgid "Uninstalled stock items" msgstr "" -#: stock/views.py:952 +#: stock/views.py:956 msgid "Adjust Stock" msgstr "" -#: stock/views.py:1062 +#: stock/views.py:1066 msgid "Move Stock Items" msgstr "" -#: stock/views.py:1063 +#: stock/views.py:1067 msgid "Count Stock Items" msgstr "" -#: stock/views.py:1064 +#: stock/views.py:1068 msgid "Remove From Stock" msgstr "" -#: stock/views.py:1065 +#: stock/views.py:1069 msgid "Add Stock Items" msgstr "" -#: stock/views.py:1066 +#: stock/views.py:1070 msgid "Delete Stock Items" msgstr "" -#: stock/views.py:1094 +#: stock/views.py:1098 msgid "Must enter integer value" msgstr "" -#: stock/views.py:1099 +#: stock/views.py:1103 msgid "Quantity must be positive" msgstr "" -#: stock/views.py:1106 +#: stock/views.py:1110 #, python-brace-format msgid "Quantity must not exceed {x}" msgstr "" -#: stock/views.py:1185 +#: stock/views.py:1189 #, python-brace-format msgid "Added stock to {n} items" msgstr "" -#: stock/views.py:1200 +#: stock/views.py:1204 #, python-brace-format msgid "Removed stock from {n} items" msgstr "" -#: stock/views.py:1213 +#: stock/views.py:1217 #, python-brace-format msgid "Counted stock for {n} items" msgstr "" -#: stock/views.py:1241 +#: stock/views.py:1245 msgid "No items were moved" msgstr "" -#: stock/views.py:1244 +#: stock/views.py:1248 #, python-brace-format msgid "Moved {n} items to {dest}" msgstr "" -#: stock/views.py:1263 +#: stock/views.py:1267 #, python-brace-format msgid "Deleted {n} stock items" msgstr "" -#: stock/views.py:1275 +#: stock/views.py:1279 msgid "Edit Stock Item" msgstr "" -#: stock/views.py:1360 +#: stock/views.py:1364 msgid "Serialize Stock" msgstr "" -#: stock/views.py:1454 templates/js/build.js:210 +#: stock/views.py:1458 templates/js/build.js:210 msgid "Create new Stock Item" msgstr "" -#: stock/views.py:1555 +#: stock/views.py:1559 msgid "Duplicate Stock Item" msgstr "" -#: stock/views.py:1621 +#: stock/views.py:1634 msgid "Invalid quantity" msgstr "" -#: stock/views.py:1624 +#: stock/views.py:1637 msgid "Quantity cannot be less than zero" msgstr "" -#: stock/views.py:1628 +#: stock/views.py:1641 msgid "Invalid part selection" msgstr "" -#: stock/views.py:1676 +#: stock/views.py:1689 #, python-brace-format msgid "Created {n} new stock items" msgstr "" -#: stock/views.py:1695 stock/views.py:1711 +#: stock/views.py:1708 stock/views.py:1724 msgid "Created new stock item" msgstr "" -#: stock/views.py:1730 +#: stock/views.py:1743 msgid "Delete Stock Location" msgstr "" -#: stock/views.py:1744 +#: stock/views.py:1757 msgid "Delete Stock Item" msgstr "" -#: stock/views.py:1756 +#: stock/views.py:1769 msgid "Delete Stock Tracking Entry" msgstr "" -#: stock/views.py:1775 +#: stock/views.py:1788 msgid "Edit Stock Tracking Entry" msgstr "" -#: stock/views.py:1785 +#: stock/views.py:1798 msgid "Add Stock Tracking Entry" msgstr "" @@ -4360,6 +4454,32 @@ msgstr "" msgid "Build Order Settings" msgstr "" +#: templates/InvenTree/settings/category.html:9 +msgid "Category Settings" +msgstr "" + +#: templates/InvenTree/settings/category.html:25 +msgid "Category Parameter Templates" +msgstr "" + +#: templates/InvenTree/settings/category.html:52 +msgid "No category parameter templates found" +msgstr "" + +#: templates/InvenTree/settings/category.html:67 +msgid "Default Value" +msgstr "" + +#: templates/InvenTree/settings/category.html:70 +#: templates/InvenTree/settings/part.html:75 +msgid "Edit Template" +msgstr "" + +#: templates/InvenTree/settings/category.html:71 +#: templates/InvenTree/settings/part.html:76 +msgid "Delete Template" +msgstr "" + #: templates/InvenTree/settings/global.html:10 msgid "Global InvenTree Settings" msgstr "" @@ -4372,22 +4492,14 @@ msgstr "" msgid "Part Options" msgstr "" -#: templates/InvenTree/settings/part.html:33 +#: templates/InvenTree/settings/part.html:34 msgid "Part Parameter Templates" msgstr "" -#: templates/InvenTree/settings/part.html:54 +#: templates/InvenTree/settings/part.html:55 msgid "No part parameter templates found" msgstr "" -#: templates/InvenTree/settings/part.html:74 -msgid "Edit Template" -msgstr "" - -#: templates/InvenTree/settings/part.html:75 -msgid "Delete Template" -msgstr "" - #: templates/InvenTree/settings/po.html:9 msgid "Purchase Order Settings" msgstr "" @@ -4434,6 +4546,10 @@ msgstr "" msgid "Global" msgstr "" +#: templates/InvenTree/settings/tabs.html:19 +msgid "Categories" +msgstr "" + #: templates/InvenTree/settings/theme.html:10 msgid "Theme Settings" msgstr "" @@ -4745,15 +4861,15 @@ msgstr "" msgid "No purchase orders found" msgstr "" -#: templates/js/order.js:180 templates/js/stock.js:677 +#: templates/js/order.js:181 templates/js/stock.js:677 msgid "Date" msgstr "" -#: templates/js/order.js:210 +#: templates/js/order.js:211 msgid "No sales orders found" msgstr "" -#: templates/js/order.js:267 +#: templates/js/order.js:268 msgid "Shipment Date" msgstr "" @@ -5163,38 +5279,38 @@ msgstr "" msgid "Important dates" msgstr "" -#: users/models.py:131 +#: users/models.py:135 msgid "Permission set" msgstr "" -#: users/models.py:139 +#: users/models.py:143 msgid "Group" msgstr "" -#: users/models.py:142 +#: users/models.py:146 msgid "View" msgstr "" -#: users/models.py:142 +#: users/models.py:146 msgid "Permission to view items" msgstr "" -#: users/models.py:144 +#: users/models.py:148 msgid "Add" msgstr "" -#: users/models.py:144 +#: users/models.py:148 msgid "Permission to add items" msgstr "" -#: users/models.py:146 +#: users/models.py:150 msgid "Change" msgstr "" -#: users/models.py:146 +#: users/models.py:150 msgid "Permissions to edit items" msgstr "" -#: users/models.py:148 +#: users/models.py:152 msgid "Permission to delete items" msgstr ""