diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 921a45c112..37a8bf82e1 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -337,7 +337,7 @@ def DownloadFile(data, filename, content_type='application/text'): return response -def ExtractSerialNumbers(serials, expected_quantity): +def extract_serial_numbers(serials, expected_quantity): """ Attempt to extract serial numbers from an input string. - Serial numbers must be integer values - Serial numbers must be positive diff --git a/InvenTree/InvenTree/static/script/inventree/inventree.js b/InvenTree/InvenTree/static/script/inventree/inventree.js index fd9c2927ce..238fc0a6a6 100644 --- a/InvenTree/InvenTree/static/script/inventree/inventree.js +++ b/InvenTree/InvenTree/static/script/inventree/inventree.js @@ -134,20 +134,35 @@ function makeProgressBar(value, maximum, opts={}) { var extraclass = ''; - if (maximum) { - // TODO - Special color? - } - else if (value > maximum) { + if (value > maximum) { extraclass='progress-bar-over'; } else if (value < maximum) { extraclass = 'progress-bar-under'; } - var text = value; + var style = options.style || ''; - if (maximum) { - text += ' / '; - text += maximum; + var text = ''; + + if (style == 'percent') { + // Display e.g. "50%" + + text = `${percent}%`; + } else if (style == 'max') { + // Display just the maximum value + text = `${maximum}`; + } else if (style == 'value') { + // Display just the current value + text = `${value}`; + } else if (style == 'blank') { + // No display! + text = ''; + } else { + /* Default style + * Display e.g. "5 / 10" + */ + + text = `${value} / ${maximum}`; } var id = options.id || 'progress-bar'; diff --git a/InvenTree/InvenTree/static/script/inventree/modals.js b/InvenTree/InvenTree/static/script/inventree/modals.js index e620489ee2..f731a5238b 100644 --- a/InvenTree/InvenTree/static/script/inventree/modals.js +++ b/InvenTree/InvenTree/static/script/inventree/modals.js @@ -134,6 +134,32 @@ function reloadFieldOptions(fieldName, options) { } +function enableField(fieldName, enabled, options={}) { + /* Enable (or disable) a particular field in a modal. + * + * Args: + * - fieldName: The name of the field + * - enabled: boolean enabled / disabled status + * - options: + */ + + var modal = options.modal || '#modal-form'; + + var field = getFieldByName(modal, fieldName); + + field.prop("disabled", !enabled); +} + +function clearField(fieldName, options={}) { + + var modal = options.modal || '#modal-form'; + + var field = getFieldByName(modal, fieldName); + + field.val(""); +} + + function partialMatcher(params, data) { /* Replacement function for the 'matcher' parameter for a select2 dropdown. @@ -696,6 +722,11 @@ function handleModalForm(url, options) { } // Form was returned, invalid! else { + + var warningDiv = $(modal).find('#form-validation-warning'); + + warningDiv.css('display', 'block'); + if (response.html_form) { injectModalForm(modal, response.html_form); @@ -813,8 +844,13 @@ function launchModalForm(url, options = {}) { $(modal).modal('hide'); - // Permission denied! - if (xhr.status == 400) { + if (xhr.status == 0) { + // No response from the server + showAlertDialog( + "No Response", + "No response from the InvenTree server", + ); + } else if (xhr.status == 400) { showAlertDialog( "Error 400: Bad Request", "Server returned error code 400" diff --git a/InvenTree/InvenTree/status_codes.py b/InvenTree/InvenTree/status_codes.py index 2032ec75d8..b527009ede 100644 --- a/InvenTree/InvenTree/status_codes.py +++ b/InvenTree/InvenTree/status_codes.py @@ -214,25 +214,25 @@ class BuildStatus(StatusCode): # Build status codes PENDING = 10 # Build is pending / active - ALLOCATED = 20 # Parts have been removed from stock + PRODUCTION = 20 # BuildOrder is in production CANCELLED = 30 # Build was cancelled COMPLETE = 40 # Build is complete options = { PENDING: _("Pending"), - ALLOCATED: _("Allocated"), + PRODUCTION: _("Production"), CANCELLED: _("Cancelled"), COMPLETE: _("Complete"), } colors = { PENDING: 'blue', - ALLOCATED: 'blue', + PRODUCTION: 'blue', COMPLETE: 'green', CANCELLED: 'red', } ACTIVE_CODES = [ PENDING, - ALLOCATED + PRODUCTION, ] diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index c46a059c8d..6630c0b0af 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -210,7 +210,7 @@ class TestSerialNumberExtraction(TestCase): def test_simple(self): - e = helpers.ExtractSerialNumbers + e = helpers.extract_serial_numbers sn = e("1-5", 5) self.assertEqual(len(sn), 5) @@ -226,7 +226,7 @@ class TestSerialNumberExtraction(TestCase): def test_failures(self): - e = helpers.ExtractSerialNumbers + e = helpers.extract_serial_numbers # Test duplicates with self.assertRaises(ValidationError): diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 465b0dc194..c59b0cdcdd 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -214,26 +214,6 @@ class AjaxMixin(InvenTreeRoleMixin): """ return {} - def pre_save(self, obj, form, **kwargs): - """ - Hook for doing something *before* an object is saved. - - obj: The object to be saved - form: The cleaned form - """ - - # Do nothing by default - pass - - def post_save(self, obj, form, **kwargs): - """ - Hook for doing something *after* an object is saved. - - """ - - # Do nothing by default - pass - def validate(self, obj, form, **kwargs): """ Hook for performing custom form validation steps. @@ -363,7 +343,7 @@ class AjaxCreateView(AjaxMixin, CreateView): form = self.get_form() return self.renderJsonResponse(request, form) - def do_save(self, form): + def save(self, form): """ Method for actually saving the form to the database. Default implementation is very simple, @@ -394,7 +374,9 @@ class AjaxCreateView(AjaxMixin, CreateView): # Extra JSON data sent alongside form data = { - 'form_valid': valid + 'form_valid': valid, + 'form_errors': self.form.errors.as_json(), + 'non_field_errors': self.form.non_field_errors().as_json(), } # Add in any extra class data @@ -403,14 +385,8 @@ class AjaxCreateView(AjaxMixin, CreateView): if valid: - # Perform (optional) pre-save step - self.pre_save(None, self.form) - # Save the object to the database - self.do_save(self.form) - - # Perform (optional) post-save step - self.post_save(self.object, self.form) + self.object = self.save(self.form) # Return the PK of the newly-created object data['pk'] = self.object.pk @@ -441,11 +417,14 @@ class AjaxUpdateView(AjaxMixin, UpdateView): return self.renderJsonResponse(request, self.get_form(), context=self.get_context_data()) - def do_save(self, form): + def save(self, object, form, **kwargs): """ Method for updating the object in the database. - Default implementation is very simple, - but can be overridden if required. + Default implementation is very simple, but can be overridden if required. + + Args: + object - The current object, to be updated + form - The validated form """ self.object = form.save() @@ -477,7 +456,9 @@ class AjaxUpdateView(AjaxMixin, UpdateView): valid = form.is_valid() data = { - 'form_valid': valid + 'form_valid': valid, + 'form_errors': form.errors.as_json(), + 'non_field_errors': form.non_field_errors().as_json(), } # Add in any extra class data @@ -486,22 +467,16 @@ class AjaxUpdateView(AjaxMixin, UpdateView): if valid: - # Perform (optional) pre-save step - self.pre_save(self.object, form) - # Save the updated objec to the database - obj = self.do_save(form) + self.save(self.object, form) - # Perform (optional) post-save step - self.post_save(obj, form) + self.object = self.get_object() # Include context data about the updated object - data['pk'] = obj.pk - - self.post_save(obj, form) + data['pk'] = self.object.pk try: - data['url'] = obj.get_absolute_url() + data['url'] = self.object.get_absolute_url() except AttributeError: pass diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index d4e458c506..2401e9d936 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -52,14 +52,16 @@ class BuildList(generics.ListCreateAPIView): queryset = super().filter_queryset(queryset) + params = self.request.query_params + # Filter by build status? - status = self.request.query_params.get('status', None) + status = params.get('status', None) if status is not None: queryset = queryset.filter(status=status) - # Filter by "active" status - active = self.request.query_params.get('active', None) + # Filter by "pending" status + active = params.get('active', None) if active is not None: active = str2bool(active) @@ -70,7 +72,7 @@ class BuildList(generics.ListCreateAPIView): queryset = queryset.exclude(status__in=BuildStatus.ACTIVE_CODES) # Filter by associated part? - part = self.request.query_params.get('part', None) + part = params.get('part', None) if part is not None: queryset = queryset.filter(part=part) @@ -119,14 +121,23 @@ class BuildItemList(generics.ListCreateAPIView): return query def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + params = self.request.query_params + # Does the user wish to filter by part? - part_pk = self.request.query_params.get('part', None) + part_pk = params.get('part', None) if part_pk: queryset = queryset.filter(stock_item__part=part_pk) + # Filter by output target + output = params.get('output', None) + + if output: + queryset = queryset.filter(install_into=output) + return queryset filter_backends = [ @@ -135,7 +146,8 @@ class BuildItemList(generics.ListCreateAPIView): filter_fields = [ 'build', - 'stock_item' + 'stock_item', + 'install_into', ] diff --git a/InvenTree/build/fixtures/build.yaml b/InvenTree/build/fixtures/build.yaml index 0ac5d00a20..cc645f9696 100644 --- a/InvenTree/build/fixtures/build.yaml +++ b/InvenTree/build/fixtures/build.yaml @@ -31,4 +31,52 @@ level: 0 lft: 0 rght: 0 - tree_id: 1 \ No newline at end of file + tree_id: 1 + +- model: build.build + pk: 3 + fields: + part: 50 + reference: "0003" + title: 'Making things' + batch: 'B2' + status: 40 # COMPLETE + quantity: 21 + notes: 'Some even more simple notes' + creation_date: '2019-03-16' + level: 0 + lft: 0 + rght: 0 + tree_id: 1 + +- model: build.build + pk: 4 + fields: + part: 50 + reference: "0004" + title: 'Making things' + batch: 'B4' + status: 40 # COMPLETE + quantity: 21 + notes: 'Some even even more simple notes' + creation_date: '2019-03-16' + level: 0 + lft: 0 + rght: 0 + tree_id: 1 + +- model: build.build + pk: 5 + fields: + part: 25 + reference: "0005" + title: "Building some Widgets" + batch: "B10" + status: 40 # Complete + quantity: 10 + creation_date: '2019-03-16' + notes: "A thing" + level: 0 + lft: 0 + rght: 0 + tree_id: 1 diff --git a/InvenTree/build/forms.py b/InvenTree/build/forms.py index 74227adb9c..24f193865d 100644 --- a/InvenTree/build/forms.py +++ b/InvenTree/build/forms.py @@ -6,11 +6,14 @@ Django Forms for interacting with Build objects from __future__ import unicode_literals from django.utils.translation import ugettext as _ +from django import forms from InvenTree.forms import HelperForm -from django import forms -from .models import Build, BuildItem -from stock.models import StockLocation +from InvenTree.fields import RoundingDecimalFormField + +from .models import Build, BuildItem, BuildOrderAttachment + +from stock.models import StockLocation, StockItem class EditBuildForm(HelperForm): @@ -21,6 +24,7 @@ class EditBuildForm(HelperForm): 'reference': 'BO', 'link': 'fa-link', 'batch': 'fa-layer-group', + 'serial-numbers': 'fa-hashtag', 'location': 'fa-map-marker-alt', } @@ -35,28 +39,149 @@ class EditBuildForm(HelperForm): 'title', 'part', 'quantity', + 'batch', + 'take_from', + 'destination', 'parent', 'sales_order', - 'take_from', - 'batch', 'link', ] -class ConfirmBuildForm(HelperForm): - """ Form for auto-allocation of stock to a build """ +class BuildOutputCreateForm(HelperForm): + """ + Form for creating a new build output. + """ - confirm = forms.BooleanField(required=False, help_text=_('Confirm')) + def __init__(self, *args, **kwargs): + + build = kwargs.pop('build', None) + + if build: + self.field_placeholder['serial_numbers'] = build.part.getSerialNumberString() + + super().__init__(*args, **kwargs) + + field_prefix = { + 'serial_numbers': 'fa-hashtag', + } + + quantity = forms.IntegerField( + label=_('Quantity'), + help_text=_('Enter quantity for build output'), + ) + + serial_numbers = forms.CharField( + label=_('Serial numbers'), + required=False, + help_text=_('Enter serial numbers for build outputs'), + ) + + confirm = forms.BooleanField( + required=True, + label=_('Confirm'), + help_text=_('Confirm creation of build outut'), + ) class Meta: model = Build fields = [ - 'confirm' + 'quantity', + 'batch', + 'serial_numbers', + 'confirm', + ] + + +class BuildOutputDeleteForm(HelperForm): + """ + Form for deleting a build output. + """ + + confirm = forms.BooleanField( + required=False, + help_text=_('Confirm deletion of build output') + ) + + output_id = forms.IntegerField( + required=True, + widget=forms.HiddenInput() + ) + + class Meta: + model = Build + fields = [ + 'confirm', + 'output_id', + ] + + +class UnallocateBuildForm(HelperForm): + """ + Form for auto-de-allocation of stock from a build + """ + + confirm = forms.BooleanField(required=False, help_text=_('Confirm unallocation of stock')) + + output_id = forms.IntegerField( + required=False, + widget=forms.HiddenInput() + ) + + part_id = forms.IntegerField( + required=False, + widget=forms.HiddenInput(), + ) + + class Meta: + model = Build + fields = [ + 'confirm', + 'output_id', + 'part_id', + ] + + +class AutoAllocateForm(HelperForm): + """ Form for auto-allocation of stock to a build """ + + confirm = forms.BooleanField(required=True, help_text=_('Confirm stock allocation')) + + # Keep track of which build output we are interested in + output = forms.ModelChoiceField( + queryset=StockItem.objects.all(), + ) + + class Meta: + model = Build + fields = [ + 'confirm', + 'output', ] class CompleteBuildForm(HelperForm): - """ Form for marking a Build as complete """ + """ + Form for marking a build as complete + """ + + confirm = forms.BooleanField( + required=True, + label=_('Confirm'), + help_text=_('Mark build as complete'), + ) + + class Meta: + model = Build + fields = [ + 'confirm', + ] + + +class CompleteBuildOutputForm(HelperForm): + """ + Form for completing a single build output + """ field_prefix = { 'serial_numbers': 'fa-hashtag', @@ -70,27 +195,32 @@ class CompleteBuildForm(HelperForm): help_text=_('Location of completed parts'), ) - serial_numbers = forms.CharField( - label=_('Serial numbers'), + confirm_incomplete = forms.BooleanField( required=False, - help_text=_('Enter unique serial numbers (or leave blank)') + help_text=_("Confirm completion with incomplete stock allocation") ) - confirm = forms.BooleanField(required=False, help_text=_('Confirm build completion')) + confirm = forms.BooleanField(required=True, help_text=_('Confirm build completion')) + + output = forms.ModelChoiceField( + queryset=StockItem.objects.all(), # Queryset is narrowed in the view + widget=forms.HiddenInput(), + ) class Meta: model = Build fields = [ - 'serial_numbers', 'location', - 'confirm' + 'output', + 'confirm', + 'confirm_incomplete', ] class CancelBuildForm(HelperForm): """ Form for cancelling a build """ - confirm_cancel = forms.BooleanField(required=False, help_text='Confirm build cancellation') + confirm_cancel = forms.BooleanField(required=False, help_text=_('Confirm build cancellation')) class Meta: model = Build @@ -100,7 +230,13 @@ class CancelBuildForm(HelperForm): class EditBuildItemForm(HelperForm): - """ Form for adding a new BuildItem to a Build """ + """ + Form for creating (or editing) a BuildItem object. + """ + + quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, help_text=_('Select quantity of stock to allocate')) + + part_id = forms.IntegerField(required=False, widget=forms.HiddenInput()) class Meta: model = BuildItem @@ -108,4 +244,19 @@ class EditBuildItemForm(HelperForm): 'build', 'stock_item', 'quantity', + 'install_into', + ] + + +class EditBuildAttachmentForm(HelperForm): + """ + Form for creating / editing a BuildAttachment object + """ + + class Meta: + model = BuildOrderAttachment + fields = [ + 'build', + 'attachment', + 'comment' ] diff --git a/InvenTree/build/migrations/0021_auto_20201020_0908_squashed_0026_auto_20201023_1228.py b/InvenTree/build/migrations/0021_auto_20201020_0908_squashed_0026_auto_20201023_1228.py new file mode 100644 index 0000000000..8db4a7f952 --- /dev/null +++ b/InvenTree/build/migrations/0021_auto_20201020_0908_squashed_0026_auto_20201023_1228.py @@ -0,0 +1,64 @@ +# Generated by Django 3.0.7 on 2020-10-25 21:33 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import mptt.fields + + +class Migration(migrations.Migration): + + replaces = [('build', '0021_auto_20201020_0908'), ('build', '0022_auto_20201020_0953'), ('build', '0023_auto_20201020_1009'), ('build', '0024_auto_20201020_1144'), ('build', '0025_auto_20201020_1248'), ('build', '0026_auto_20201023_1228')] + + dependencies = [ + ('stock', '0052_stockitem_is_building'), + ('build', '0020_auto_20201019_1325'), + ('part', '0051_bomitem_optional'), + ] + + operations = [ + migrations.AddField( + model_name='builditem', + name='install_into', + field=models.ForeignKey(blank=True, help_text='Destination stock item', limit_choices_to={'is_building': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='items_to_install', to='stock.StockItem'), + ), + migrations.AlterField( + model_name='builditem', + name='stock_item', + field=models.ForeignKey(help_text='Source stock item', limit_choices_to={'belongs_to': None, 'build_order': None, 'sales_order': None}, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='stock.StockItem'), + ), + migrations.AddField( + model_name='build', + name='destination', + field=models.ForeignKey(blank=True, help_text='Select location where the completed items will be stored', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='incoming_builds', to='stock.StockLocation', verbose_name='Destination Location'), + ), + migrations.AlterField( + model_name='build', + name='parent', + field=mptt.fields.TreeForeignKey(blank=True, help_text='BuildOrder to which this build is allocated', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='build.Build', verbose_name='Parent Build'), + ), + migrations.AlterField( + model_name='build', + name='status', + field=models.PositiveIntegerField(choices=[(10, 'Pending'), (20, 'Production'), (30, 'Cancelled'), (40, 'Complete')], default=10, help_text='Build status code', validators=[django.core.validators.MinValueValidator(0)], verbose_name='Build Status'), + ), + migrations.AlterField( + model_name='build', + name='part', + field=models.ForeignKey(help_text='Select part to build', limit_choices_to={'active': True, 'assembly': True, 'virtual': False}, on_delete=django.db.models.deletion.CASCADE, related_name='builds', to='part.Part', verbose_name='Part'), + ), + migrations.AddField( + model_name='build', + name='completed', + field=models.PositiveIntegerField(default=0, help_text='Number of stock items which have been completed', verbose_name='Completed items'), + ), + migrations.AlterField( + model_name='build', + name='quantity', + field=models.PositiveIntegerField(default=1, help_text='Number of stock items to build', validators=[django.core.validators.MinValueValidator(1)], verbose_name='Build Quantity'), + ), + migrations.AlterUniqueTogether( + name='builditem', + unique_together={('build', 'stock_item', 'install_into')}, + ), + ] diff --git a/InvenTree/build/migrations/0022_buildorderattachment.py b/InvenTree/build/migrations/0022_buildorderattachment.py new file mode 100644 index 0000000000..0256649027 --- /dev/null +++ b/InvenTree/build/migrations/0022_buildorderattachment.py @@ -0,0 +1,31 @@ +# Generated by Django 3.0.7 on 2020-10-26 04:17 + +import InvenTree.models +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('build', '0021_auto_20201020_0908_squashed_0026_auto_20201023_1228'), + ] + + operations = [ + migrations.CreateModel( + name='BuildOrderAttachment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('attachment', models.FileField(help_text='Select file to attach', upload_to=InvenTree.models.rename_attachment)), + ('comment', models.CharField(blank=True, help_text='File comment', max_length=100)), + ('upload_date', models.DateField(auto_now_add=True, null=True)), + ('build', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='build.Build')), + ('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 877affd144..6634b69cdf 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -5,6 +5,7 @@ Build database model definitions # -*- coding: utf-8 -*- from __future__ import unicode_literals +import os from datetime import datetime from django.contrib.auth.models import User @@ -22,8 +23,9 @@ from markdownx.models import MarkdownxField from mptt.models import MPTTModel, TreeForeignKey from InvenTree.status_codes import BuildStatus -from InvenTree.helpers import increment, getSetting +from InvenTree.helpers import increment, getSetting, normalize from InvenTree.validators import validate_build_order_reference +from InvenTree.models import InvenTreeAttachment import InvenTree.fields @@ -32,7 +34,7 @@ from part import models as PartModels class Build(MPTTModel): - """ A Build object organises the creation of new parts from the component parts. + """ A Build object organises the creation of new StockItem objects from other existing StockItem objects. Attributes: part: The part to be built (from component BOM items) @@ -62,23 +64,7 @@ class Build(MPTTModel): def get_absolute_url(self): return reverse('build-detail', kwargs={'pk': self.id}) - - def clean(self): - """ - Validation for Build object. - """ - - super().clean() - - try: - if self.part.trackable: - if not self.quantity == int(self.quantity): - raise ValidationError({ - 'quantity': _("Build quantity must be integer value for trackable parts") - }) - except PartModels.Part.DoesNotExist: - pass - + reference = models.CharField( unique=True, max_length=64, @@ -103,7 +89,7 @@ class Build(MPTTModel): blank=True, null=True, related_name='children', verbose_name=_('Parent Build'), - help_text=_('Parent build to which this build is allocated'), + help_text=_('BuildOrder to which this build is allocated'), ) part = models.ForeignKey( @@ -112,7 +98,6 @@ class Build(MPTTModel): on_delete=models.CASCADE, related_name='builds', limit_choices_to={ - 'is_template': False, 'assembly': True, 'active': True, 'virtual': False, @@ -138,11 +123,26 @@ class Build(MPTTModel): help_text=_('Select location to take stock from for this build (leave blank to take from any stock location)') ) + destination = models.ForeignKey( + 'stock.StockLocation', + verbose_name=_('Destination Location'), + on_delete=models.SET_NULL, + related_name='incoming_builds', + null=True, blank=True, + help_text=_('Select location where the completed items will be stored'), + ) + quantity = models.PositiveIntegerField( verbose_name=_('Build Quantity'), default=1, validators=[MinValueValidator(1)], - help_text=_('Number of parts to build') + help_text=_('Number of stock items to build') + ) + + completed = models.PositiveIntegerField( + verbose_name=_('Completed items'), + default=0, + help_text=_('Number of stock items which have been completed') ) status = models.PositiveIntegerField( @@ -182,10 +182,96 @@ class Build(MPTTModel): blank=True, help_text=_('Extra build notes') ) + @property + def bom_items(self): + """ + Returns the BOM items for the part referenced by this BuildOrder + """ + + return self.part.bom_items.all().prefetch_related( + 'sub_part' + ) + + @property + def remaining(self): + """ + Return the number of outputs remaining to be completed. + """ + + return max(0, self.quantity - self.completed) + @property def output_count(self): return self.build_outputs.count() + def get_build_outputs(self, **kwargs): + """ + Return a list of build outputs. + + kwargs: + complete = (True / False) - If supplied, filter by completed status + in_stock = (True / False) - If supplied, filter by 'in-stock' status + """ + + outputs = self.build_outputs.all() + + # Filter by 'in stock' status + in_stock = kwargs.get('in_stock', None) + + if in_stock is not None: + if in_stock: + outputs = outputs.filter(StockModels.StockItem.IN_STOCK_FILTER) + else: + outputs = outputs.exclude(StockModels.StockItem.IN_STOCK_FILTER) + + # Filter by 'complete' status + complete = kwargs.get('complete', None) + + if complete is not None: + if complete: + outputs = outputs.filter(is_building=False) + else: + outputs = outputs.filter(is_building=True) + + return outputs + + @property + def complete_outputs(self): + """ + Return all the "completed" build outputs + """ + + outputs = self.get_build_outputs(complete=True) + + # TODO - Ordering? + + return outputs + + @property + def incomplete_outputs(self): + """ + Return all the "incomplete" build outputs + """ + + outputs = self.get_build_outputs(complete=False) + + # TODO - Order by how "complete" they are? + + return outputs + + @property + def incomplete_count(self): + """ + Return the total number of "incomplete" outputs + """ + + quantity = 0 + + for output in self.incomplete_outputs: + quantity += output.quantity + + return quantity + @classmethod def getNextBuildNumber(cls): """ @@ -218,6 +304,42 @@ class Build(MPTTModel): return new_ref + @property + def can_complete(self): + """ + Returns True if this build can be "completed" + + - Must not have any outstanding build outputs + - 'completed' value must meet (or exceed) the 'quantity' value + """ + + if self.incomplete_count > 0: + return False + + if self.completed < self.quantity: + return False + + # No issues! + return True + + @transaction.atomic + def complete_build(self, user): + """ + Mark this build as complete + """ + + if not self.can_complete: + return + + self.completion_date = datetime.now().date() + self.completed_by = user + self.status = BuildStatus.COMPLETE + self.save() + + # Ensure that there are no longer any BuildItem objects + # which point to thie Build Order + self.allocated_stock.all().delete() + @transaction.atomic def cancelBuild(self, user): """ Mark the Build as CANCELLED @@ -237,58 +359,77 @@ class Build(MPTTModel): self.status = BuildStatus.CANCELLED self.save() - def getAutoAllocations(self): - """ Return a list of parts which will be allocated + def getAutoAllocations(self, output): + """ + Return a list of StockItem objects which will be allocated using the 'AutoAllocate' function. - For each item in the BOM for the attached Part: - - - If there is a single StockItem, use that StockItem - - Take as many parts as available (up to the quantity required for the BOM) - - If there are multiple StockItems available, ignore (leave up to the user) + For each item in the BOM for the attached Part, + the following tests must *all* evaluate to True, + for the part to be auto-allocated: + - The sub_item in the BOM line must *not* be trackable + - There is only a single stock item available (which has not already been allocated to this build) + - The stock item has an availability greater than zero + Returns: - A list object containing the StockItem objects to be allocated (and the quantities) + A list object containing the StockItem objects to be allocated (and the quantities). + Each item in the list is a dict as follows: + { + 'stock_item': stock_item, + 'quantity': stock_quantity, + } """ allocations = [] - for item in self.part.bom_items.all().prefetch_related('sub_part'): + """ + Iterate through each item in the BOM + """ - # How many parts required for this build? - q_required = item.quantity * self.quantity + for bom_item in self.bom_items: - # Grab a list of StockItem objects which are "in stock" - stock = StockModels.StockItem.objects.filter(StockModels.StockItem.IN_STOCK_FILTER) - - # Filter by part reference - stock = stock.filter(part=item.sub_part) + part = bom_item.sub_part + + # Skip any parts which are already fully allocated + if self.isPartFullyAllocated(part, output): + continue + + # How many parts are required to complete the output? + required = self.unallocatedQuantity(part, output) + + # Grab a list of stock items which are available + stock_items = self.availableStockItems(part, output) # Ensure that the available stock items are in the correct location if self.take_from is not None: # Filter for stock that is located downstream of the designated location - stock = stock.filter(location__in=[loc for loc in self.take_from.getUniqueChildren()]) + stock_items = stock_items.filter(location__in=[loc for loc in self.take_from.getUniqueChildren()]) # Only one StockItem to choose from? Default to that one! - if len(stock) == 1: - stock_item = stock[0] + if stock_items.count() == 1: + stock_item = stock_items[0] - # Check that we have not already allocated this stock-item against this build - build_items = BuildItem.objects.filter(build=self, stock_item=stock_item) + # Double check that we have not already allocated this stock-item against this build + build_items = BuildItem.objects.filter( + build=self, + stock_item=stock_item, + install_into=output + ) if len(build_items) > 0: continue - # Are there any parts available? + # How many items are actually available? if stock_item.quantity > 0: # Only take as many as are available - if stock_item.quantity < q_required: - q_required = stock_item.quantity + if stock_item.quantity < required: + required = stock_item.quantity allocation = { 'stock_item': stock_item, - 'quantity': q_required, + 'quantity': required, } allocations.append(allocation) @@ -296,14 +437,130 @@ class Build(MPTTModel): return allocations @transaction.atomic - def unallocateStock(self): - """ Deletes all stock allocations for this build. """ + def unallocateStock(self, output=None, part=None): + """ + Deletes all stock allocations for this build. + + Args: + output: Specify which build output to delete allocations (optional) - BuildItem.objects.filter(build=self.id).delete() + """ + + allocations = BuildItem.objects.filter(build=self.pk) + + if output: + allocations = allocations.filter(install_into=output.pk) + + if part: + allocations = allocations.filter(stock_item__part=part) + + # Remove all the allocations + allocations.delete() @transaction.atomic - def autoAllocate(self): - """ Run auto-allocation routine to allocate StockItems to this Build. + def create_build_output(self, quantity, **kwargs): + """ + Create a new build output against this BuildOrder. + + args: + quantity: The quantity of the item to produce + + kwargs: + batch: Override batch code + serials: Serial numbers + location: Override location + """ + + batch = kwargs.get('batch', self.batch) + location = kwargs.get('location', self.destination) + serials = kwargs.get('serials', None) + + """ + Determine if we can create a single output (with quantity > 0), + or multiple outputs (with quantity = 1) + """ + + multiple = False + + # Serial numbers are provided? We need to split! + if serials: + multiple = True + + # BOM has trackable parts, so we must split! + if self.part.has_trackable_parts: + multiple = True + + if multiple: + """ + Create multiple build outputs with a single quantity of 1 + """ + + for ii in range(quantity): + + if serials: + serial = serials[ii] + else: + serial = None + + StockModels.StockItem.objects.create( + quantity=1, + location=location, + part=self.part, + build=self, + batch=batch, + serial=serial, + is_building=True, + ) + + else: + """ + Create a single build output of the given quantity + """ + + StockModels.StockItem.objects.create( + quantity=quantity, + location=location, + part=self.part, + build=self, + batch=batch, + is_building=True + ) + + if self.status == BuildStatus.PENDING: + self.status = BuildStatus.PRODUCTION + self.save() + + @transaction.atomic + def deleteBuildOutput(self, output): + """ + Remove a build output from the database: + + - Unallocate any build items against the output + - Delete the output StockItem + """ + + if not output: + raise ValidationError(_("No build output specified")) + + if not output.is_building: + raise ValidationError(_("Build output is already completed")) + + if not output.build == self: + raise ValidationError(_("Build output does not match Build Order")) + + # Unallocate all build items against the output + self.unallocateStock(output) + + # Remove the build output from the database + output.delete() + + @transaction.atomic + def autoAllocate(self, output): + """ + Run auto-allocation routine to allocate StockItems to this Build. + + Args: + output: If specified, only auto-allocate against the given built output Returns a list of dict objects with keys like: @@ -315,128 +572,171 @@ class Build(MPTTModel): See: getAutoAllocations() """ - allocations = self.getAutoAllocations() + allocations = self.getAutoAllocations(output) for item in allocations: # Create a new allocation build_item = BuildItem( build=self, stock_item=item['stock_item'], - quantity=item['quantity']) + quantity=item['quantity'], + install_into=output, + ) build_item.save() @transaction.atomic - def completeBuild(self, location, serial_numbers, user): - """ Mark the Build as COMPLETE + def completeBuildOutput(self, output, user, **kwargs): + """ + Complete a particular build output - - Takes allocated items from stock - - Delete pending BuildItem objects + - Remove allocated StockItems + - Mark the output as complete """ - # Complete the build allocation for each BuildItem - for build_item in self.allocated_stock.all().prefetch_related('stock_item'): + # List the allocated BuildItem objects for the given output + allocated_items = output.items_to_install.all() + + for build_item in allocated_items: + + # TODO: This is VERY SLOW as each deletion from the database takes ~1 second to complete + # TODO: Use celery / redis to offload the actual object deletion... + # REF: https://www.botreetechnologies.com/blog/implementing-celery-using-django-for-background-task-processing + # REF: https://code.tutsplus.com/tutorials/using-celery-with-django-for-background-task-processing--cms-28732 + + # Complete the allocation of stock for that item build_item.complete_allocation(user) - # Check that the stock-item has been assigned to this build, and remove the builditem from the database - if build_item.stock_item.build_order == self: - build_item.delete() + # Delete the BuildItem objects from the database + allocated_items.all().delete() - notes = 'Built {q} on {now}'.format( - q=self.quantity, - now=str(datetime.now().date()) + # Ensure that the output is updated correctly + output.build = self + output.is_building = False + + output.save() + + output.addTransactionNote( + _('Completed build output'), + user, + system=True ) - # Generate the build outputs - if self.part.trackable and serial_numbers: - # Add new serial numbers - for serial in serial_numbers: - item = StockModels.StockItem.objects.create( - part=self.part, - build=self, - location=location, - quantity=1, - serial=serial, - batch=str(self.batch) if self.batch else '', - notes=notes - ) - - item.save() - - else: - # Add stock of the newly created item - item = StockModels.StockItem.objects.create( - part=self.part, - build=self, - location=location, - quantity=self.quantity, - batch=str(self.batch) if self.batch else '', - notes=notes - ) - - item.save() - - # Finally, mark the build as complete - self.completion_date = datetime.now().date() - self.completed_by = user - self.status = BuildStatus.COMPLETE + # Increase the completed quantity for this build + self.completed += output.quantity self.save() - return True - - def isFullyAllocated(self): + def requiredQuantity(self, part, output): """ - Return True if this build has been fully allocated. - """ - - bom_items = self.part.bom_items.all() - - for item in bom_items: - part = item.sub_part - - if not self.isPartFullyAllocated(part): - return False - - return True - - def isPartFullyAllocated(self, part): - """ - Check if a given Part is fully allocated for this Build - """ - - return self.getAllocatedQuantity(part) >= self.getRequiredQuantity(part) - - def getRequiredQuantity(self, part): - """ Calculate the quantity of required to make this build. + Get the quantity of a part required to complete the particular build output. + + Args: + part: The Part object + output - The particular build output (StockItem) """ + # Extract the BOM line item from the database try: - item = PartModels.BomItem.objects.get(part=self.part.id, sub_part=part.id) - q = item.quantity - except PartModels.BomItem.DoesNotExist: - q = 0 + bom_item = PartModels.BomItem.objects.get(part=self.part.pk, sub_part=part.pk) + quantity = bom_item.quantity + except (PartModels.BomItem.DoesNotExist): + quantity = 0 - return q * self.quantity + if output: + quantity *= output.quantity + else: + quantity *= self.remaining - def getAllocatedQuantity(self, part): - """ Calculate the total number of currently allocated to this build + return quantity + + def allocatedItems(self, part, output): + """ + Return all BuildItem objects which allocate stock of to + + Args: + part - The part object + output - Build output (StockItem). """ - allocated = BuildItem.objects.filter(build=self.id, stock_item__part=part.id).aggregate(q=Coalesce(Sum('quantity'), 0)) + allocations = BuildItem.objects.filter( + build=self, + stock_item__part=part, + install_into=output, + ) + + return allocations + + def allocatedQuantity(self, part, output): + """ + Return the total quantity of given part allocated to a given build output. + """ + + allocations = self.allocatedItems(part, output) + + allocated = allocations.aggregate(q=Coalesce(Sum('quantity'), 0)) return allocated['q'] - def getUnallocatedQuantity(self, part): - """ Calculate the quantity of which still needs to be allocated to this build. - - Args: - Part - the part to be tested - - Returns: - The remaining allocated quantity + def unallocatedQuantity(self, part, output): + """ + Return the total unallocated (remaining) quantity of a part against a particular output. """ - return max(self.getRequiredQuantity(part) - self.getAllocatedQuantity(part), 0) + required = self.requiredQuantity(part, output) + allocated = self.allocatedQuantity(part, output) + + return max(required - allocated, 0) + + def isPartFullyAllocated(self, part, output): + """ + Returns True if the part has been fully allocated to the particular build output + """ + + return self.unallocatedQuantity(part, output) == 0 + + def isFullyAllocated(self, output): + """ + Returns True if the particular build output is fully allocated. + """ + + for bom_item in self.bom_items: + part = bom_item.sub_part + + if not self.isPartFullyAllocated(part, output): + return False + + # All parts must be fully allocated! + return True + + def allocatedParts(self, output): + """ + Return a list of parts which have been fully allocated against a particular output + """ + + allocated = [] + + for bom_item in self.bom_items: + part = bom_item.sub_part + + if self.isPartFullyAllocated(part, output): + allocated.append(part) + + return allocated + + def unallocatedParts(self, output): + """ + Return a list of parts which have *not* been fully allocated against a particular output + """ + + unallocated = [] + + for bom_item in self.bom_items: + part = bom_item.sub_part + + if not self.isPartFullyAllocated(part, output): + unallocated.append(part) + + return unallocated @property def required_parts(self): @@ -444,26 +744,44 @@ class Build(MPTTModel): parts = [] for item in self.part.bom_items.all().prefetch_related('sub_part'): - part = { - 'part': item.sub_part, - 'per_build': item.quantity, - 'quantity': item.quantity * self.quantity, - 'allocated': self.getAllocatedQuantity(item.sub_part) - } - - parts.append(part) + parts.append(item.sub_part) return parts - @property - def can_build(self): - """ Return true if there are enough parts to supply build """ + def availableStockItems(self, part, output): + """ + Returns stock items which are available for allocation to this build. - for item in self.required_parts: - if item['part'].total_stock < item['quantity']: - return False + Args: + part - Part object + output - The particular build output + """ - return True + # Grab initial query for items which are "in stock" and match the part + items = StockModels.StockItem.objects.filter( + StockModels.StockItem.IN_STOCK_FILTER + ) + + items = items.filter(part=part) + + # Exclude any items which have already been allocated + allocated = BuildItem.objects.filter( + build=self, + stock_item__part=part, + install_into=output, + ) + + items = items.exclude( + id__in=[item.stock_item.id for item in allocated.all()] + ) + + # Limit query to stock items which are "downstream" of the source location + if self.take_from is not None: + items = items.filter( + location__in=[loc for loc in self.take_from.getUniqueChildren()] + ) + + return items @property def is_active(self): @@ -478,9 +796,21 @@ class Build(MPTTModel): @property def is_complete(self): """ Returns True if the build status is COMPLETE """ + return self.status == BuildStatus.COMPLETE +class BuildOrderAttachment(InvenTreeAttachment): + """ + Model for storing file attachments against a BuildOrder object + """ + + def getSubdir(self): + return os.path.join('bo_files', str(self.build.id)) + + build = models.ForeignKey(Build, on_delete=models.CASCADE, related_name='attachments') + + class BuildItem(models.Model): """ A BuildItem links multiple StockItem objects to a Build. These are used to allocate part stock to a build. @@ -500,9 +830,38 @@ class BuildItem(models.Model): class Meta: unique_together = [ - ('build', 'stock_item'), + ('build', 'stock_item', 'install_into'), ] + def save(self, *args, **kwargs): + + self.validate_unique() + self.clean() + + super().save() + + def validate_unique(self, exclude=None): + """ + Test that this BuildItem object is "unique". + Essentially we do not want a stock_item being allocated to a Build multiple times. + """ + + super().validate_unique(exclude) + + items = BuildItem.objects.exclude(id=self.id).filter( + build=self.build, + stock_item=self.stock_item, + install_into=self.install_into + ) + + if items.exists(): + msg = _("BuildItem must be unique for build, stock_item and install_into") + raise ValidationError({ + 'build': msg, + 'stock_item': msg, + 'install_into': msg + }) + def clean(self): """ Check validity of the BuildItem model. The following checks are performed: @@ -510,28 +869,38 @@ class BuildItem(models.Model): - StockItem.part must be in the BOM of the Part object referenced by Build - Allocation quantity cannot exceed available quantity """ + + self.validate_unique() - super(BuildItem, self).clean() + super().clean() errors = {} + if not self.install_into: + raise ValidationError(_('Build item must specify a build output')) + try: - if self.stock_item.part not in self.build.part.required_parts(): + # Allocated part must be in the BOM for the master part + if self.stock_item.part not in self.build.part.getRequiredParts(recursive=False): errors['stock_item'] = [_("Selected stock item not found in BOM for part '{p}'".format(p=self.build.part.full_name))] + # Allocated quantity cannot exceed available stock quantity if self.quantity > self.stock_item.quantity: errors['quantity'] = [_("Allocated quantity ({n}) must not exceed available quantity ({q})".format( - n=self.quantity, - q=self.stock_item.quantity + n=normalize(self.quantity), + q=normalize(self.stock_item.quantity) ))] + # Allocated quantity cannot cause the stock item to be over-allocated if self.stock_item.quantity - self.stock_item.allocation_count() + self.quantity < self.quantity: errors['quantity'] = _('StockItem is over-allocated') + # Allocated quantity must be positive if self.quantity <= 0: errors['quantity'] = _('Allocation quantity must be greater than zero') - if self.stock_item.serial and not self.quantity == 1: + # Quantity must be 1 for serialized stock + if self.stock_item.serialized and not self.quantity == 1: errors['quantity'] = _('Quantity must be 1 for serialized stock') except (StockModels.StockItem.DoesNotExist, PartModels.Part.DoesNotExist): @@ -540,22 +909,33 @@ class BuildItem(models.Model): if len(errors) > 0: raise ValidationError(errors) + @transaction.atomic def complete_allocation(self, user): + """ + Complete the allocation of this BuildItem into the output stock item. + + - If the referenced part is trackable, the stock item will be *installed* into the build output + - If the referenced part is *not* trackable, the stock item will be removed from stock + """ item = self.stock_item - # Split the allocated stock if there are more available than allocated - if item.quantity > self.quantity: - item = item.splitStock(self.quantity, None, user) + # For a trackable part, special consideration needed! + if item.part.trackable: + # Split the allocated stock if there are more available than allocated + if item.quantity > self.quantity: + item = item.splitStock(self.quantity, None, user) - # Update our own reference to the new item - self.stock_item = item - self.save() + # Make sure we are pointing to the new item + self.stock_item = item + self.save() - # TODO - If the item__part object is not trackable, delete the stock item here - - item.build_order = self.build - item.save() + # Install the stock item into the output + item.belongs_to = self.install_into + item.save() + else: + # Simply remove the items from stock + item.take_stock(self.quantity, user) build = models.ForeignKey( Build, @@ -568,7 +948,7 @@ class BuildItem(models.Model): 'stock.StockItem', on_delete=models.CASCADE, related_name='allocations', - help_text=_('Stock Item to allocate to build'), + help_text=_('Source stock item'), limit_choices_to={ 'build_order': None, 'sales_order': None, @@ -583,3 +963,14 @@ class BuildItem(models.Model): validators=[MinValueValidator(0)], help_text=_('Stock quantity to allocate to build') ) + + install_into = models.ForeignKey( + 'stock.StockItem', + on_delete=models.SET_NULL, + blank=True, null=True, + related_name='items_to_install', + help_text=_('Destination stock item'), + limit_choices_to={ + 'is_building': True, + } + ) diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 53e75942f0..4423619b8a 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -38,6 +38,7 @@ class BuildSerializer(InvenTreeModelSerializer): 'url', 'title', 'creation_date', + 'completed', 'completion_date', 'part', 'part_detail', @@ -51,9 +52,10 @@ class BuildSerializer(InvenTreeModelSerializer): ] read_only_fields = [ - 'status', + 'completed', 'creation_date', 'completion_data', + 'status', 'status_text', ] @@ -73,6 +75,7 @@ class BuildItemSerializer(InvenTreeModelSerializer): fields = [ 'pk', 'build', + 'install_into', 'part', 'part_name', 'part_image', diff --git a/InvenTree/build/templates/build/allocate.html b/InvenTree/build/templates/build/allocate.html index bf43d0ca85..e811c37ce8 100644 --- a/InvenTree/build/templates/build/allocate.html +++ b/InvenTree/build/templates/build/allocate.html @@ -11,404 +11,68 @@ InvenTree | Allocate Parts {% include "build/tabs.html" with tab='allocate' %} -
- {% if build.status == BuildStatus.PENDING %} -
- - - -
- {% endif %} +

{% trans "Incomplete Build Ouputs" %}

+ +
+ +{% if build.is_complete %} +
+ {% trans "Build order has been completed" %}
- -
+{% else %} +
+ + + +
+ +
+{% if build.incomplete_outputs %} +
+ {% for item in build.incomplete_outputs %} + {% include "build/allocation_card.html" with item=item %} + {% endfor %} +
+{% else %} +
+ {% trans "Create a new build output" %}
+ {% trans "No incomplete build outputs remain." %}
+ {% trans "Create a new build output using the button above" %} +
+{% endif %} + +{% endif %} {% endblock %} {% block js_ready %} {{ block.super }} - var buildTable = $("#build-item-list"); + var buildInfo = { + pk: {{ build.pk }}, + quantity: {{ build.quantity }}, + completed: {{ build.completed }}, + part: {{ build.part.pk }}, + }; - // Calculate sum of allocations for a particular table row - function sumAllocations(row) { - if (row.allocations == null) { - return 0; - } - - var quantity = 0; - - row.allocations.forEach(function(item) { - quantity += item.quantity; - }); - - return quantity; - } - - function getUnallocated(row) { - // Return the number of items remaining to be allocated for a given row - return {{ build.quantity }} * row.quantity - sumAllocations(row); - } - - function setExpandedAllocatedLocation(row) { - // Handle case when stock item does not have a location set - if (row.location_detail == null) { - return 'No stock location set'; - } else { - return row.location_detail.pathstring; - } - } - - function reloadTable() { - // Reload the build allocation table - buildTable.bootstrapTable('refresh'); - } - - function setupCallbacks() { - // Register button callbacks once the table data are loaded - - buildTable.find(".button-add").click(function() { - var pk = $(this).attr('pk'); - - // Extract row data from the table - var idx = $(this).closest('tr').attr('data-index'); - var row = buildTable.bootstrapTable('getData')[idx]; - - launchModalForm('/build/item/new/', { - success: reloadTable, - data: { - part: row.sub_part, - build: {{ build.id }}, - quantity: getUnallocated(row), - }, - secondary: [ - { - field: 'stock_item', - label: '{% trans "New Stock Item" %}', - title: '{% trans "Create new Stock Item" %}', - url: '{% url "stock-item-create" %}', - data: { - part: row.sub_part, - }, - }, - ] - }); - }); - - - buildTable.find(".button-build").click(function() { - // Start a new build for the sub_part - - var pk = $(this).attr('pk'); - - // Extract row data from the table - var idx = $(this).closest('tr').attr('data-index'); - var row = buildTable.bootstrapTable('getData')[idx]; - - launchModalForm('/build/new/', { - follow: true, - data: { - part: row.sub_part, - parent: {{ build.id }}, - quantity: getUnallocated(row), - }, - }); - - }); - - buildTable.find(".button-buy").click(function() { - var pk = $(this).attr('pk'); - - // Extract row data from the table - var idx = $(this).closest('tr').attr('data-index'); - var row = buildTable.bootstrapTable('getData')[idx]; - - launchModalForm("{% url 'order-parts' %}", { - data: { - parts: [row.sub_part], - }, - }); - }); - } - - buildTable.inventreeTable({ - uniqueId: 'sub_part', - url: "{% url 'api-bom-list' %}", - onPostBody: setupCallbacks, - detailViewByClick: true, - detailView: true, - detailFilter: function(index, row) { - return row.allocations != null; - }, - detailFormatter: function(index, row, element) { - // Construct an 'inner table' which shows the stock allocations - - var subTableId = `allocation-table-${row.pk}`; - - var html = `
`; - - element.html(html); - - var lineItem = row; - - var subTable = $(`#${subTableId}`); - - subTable.bootstrapTable({ - data: row.allocations, - showHeader: true, - columns: [ - { - width: '50%', - field: 'quantity', - title: 'Quantity', - formatter: function(value, row, index, field) { - var text = ''; - - var url = ''; - - if (row.serial && row.quantity == 1) { - text = `{% trans "Serial Number" %}: ${row.serial}`; - } else { - text = `{% trans "Quantity" %}: ${row.quantity}`; - } - - {% if build.status == BuildStatus.COMPLETE %} - url = `/stock/item/${row.pk}/`; - {% else %} - url = `/stock/item/${row.stock_item}/`; - {% endif %} - - return renderLink(text, url); - }, - }, - { - field: 'location', - title: '{% trans "Location" %}', - formatter: function(value, row, index, field) { - {% if build.status == BuildStatus.COMPLETE %} - var text = setExpandedAllocatedLocation(row); - var url = `/stock/location/${row.location}/`; - {% else %} - var text = row.stock_item_detail.location_name; - var url = `/stock/location/${row.stock_item_detail.location}/`; - {% endif %} - - return renderLink(text, url); - } - }, - {% if build.status == BuildStatus.PENDING %} - { - field: 'buttons', - title: 'Actions', - formatter: function(value, row) { - - var pk = row.pk; - - var html = `
`; - - {% if build.status == BuildStatus.PENDING %} - html += makeIconButton('fa-edit icon-blue', 'button-allocation-edit', pk, '{% trans "Edit stock allocation" %}'); - html += makeIconButton('fa-trash-alt icon-red', 'button-allocation-delete', pk, '{% trans "Delete stock allocation" %}'); - {% endif %} - - html += `
`; - - return html; - }, - }, - {% endif %} - ] - }); - - // Assign button callbacks to the newly created allocation buttons - subTable.find(".button-allocation-edit").click(function() { - var pk = $(this).attr('pk'); - launchModalForm(`/build/item/${pk}/edit/`, { - success: reloadTable, - }); - }); - - subTable.find('.button-allocation-delete').click(function() { - var pk = $(this).attr('pk'); - launchModalForm(`/build/item/${pk}/delete/`, { - success: reloadTable, - }); - }); - }, - formatNoMatches: function() { return "{% trans 'No BOM items found' %}"; }, - onLoadSuccess: function(tableData) { - // Once the BOM data are loaded, request allocation data for the build - {% if build.status == BuildStatus.COMPLETE %} - // Request StockItem which have been assigned to this build - inventreeGet('/api/stock/', - { - build_order: {{ build.id }}, - location_detail: true, - }, - { - success: function(data) { - // Iterate through the returned data, group by "part", - var allocations = {}; - - data.forEach(function(item) { - // Group allocations by referenced 'part' - var key = parseInt(item.part); - - if (!(key in allocations)) { - allocations[key] = new Array(); - } - - allocations[key].push(item); - }); - - for (var key in allocations) { - - var tableRow = buildTable.bootstrapTable('getRowByUniqueId', key); - - tableRow.allocations = allocations[key]; - - buildTable.bootstrapTable('updateByUniqueId', key, tableRow, true); - } - }, - }, - ); - - {% else %} - inventreeGet('/api/build/item/', - { - build: {{ build.id }}, - }, - { - success: function(data) { - - // Iterate through the returned data, and group by "part" - var allocations = {}; - - data.forEach(function(item) { - - // Group allocations by referenced 'part' - var part = item.part; - var key = parseInt(part); - - if (!(key in allocations)) { - allocations[key] = new Array(); - } - - // Add the allocation to the list - allocations[key].push(item); - }); - - for (var key in allocations) { - - // Select the associated row in the table - var tableRow = buildTable.bootstrapTable('getRowByUniqueId', key); - - // Set the allocations for the row - tableRow.allocations = allocations[key]; - - // And push the updated row back into the main table - buildTable.bootstrapTable('updateByUniqueId', key, tableRow, true); - } - } - }, - ); - {% endif %} - }, - queryParams: { - part: {{ build.part.id }}, - sub_part_detail: 1, - }, - columns: [ - { - field: 'id', - visible: false, - }, - { - sortable: true, - field: 'sub_part', - title: '{% trans "Part" %}', - formatter: function(value, row, index, field) { - return imageHoverIcon(row.sub_part_detail.thumbnail) + renderLink(row.sub_part_detail.full_name, `/part/${row.sub_part}/`); - }, - }, - { - sortable: true, - field: 'sub_part_detail.description', - title: '{% trans "Description" %}', - }, - { - sortable: true, - field: 'reference', - title: '{% trans "Reference" %}', - }, - { - sortable: true, - field: 'quantity', - title: '{% trans "Required" %}', - formatter: function(value, row) { - return value * {{ build.quantity }}; - }, - }, - { - sortable: true, - field: 'allocated', - {% if build.status == BuildStatus.COMPLETE %} - title: '{% trans "Assigned" %}', - {% else %} - title: '{% trans "Allocated" %}', - {% endif %} - formatter: function(value, row) { - - var allocated = sumAllocations(row); - - return makeProgressBar(allocated, row.quantity * {{ build.quantity }}); - }, - sorter: function(valA, valB, rowA, rowB) { - - var aA = sumAllocations(rowA); - var aB = sumAllocations(rowB); - - var qA = rowA.quantity * {{ build.quantity }}; - var qB = rowB.quantity * {{ build.quantity }}; - - if (aA == 0 && aB == 0) { - return (qA > qB) ? 1 : -1; - } - - var progressA = parseFloat(aA) / qA; - var progressB = parseFloat(aB) / qB; - - return (progressA < progressB) ? 1 : -1; - } - }, - {% if build.status == BuildStatus.PENDING %} - { - field: 'buttons', - formatter: function(value, row, index, field) { - - var html = `
`; - var pk = row.sub_part; - - {% if build.status == BuildStatus.PENDING %} - if (row.sub_part_detail.purchaseable) { - html += makeIconButton('fa-shopping-cart', 'button-buy', pk, '{% trans "Buy parts" %}'); - } - - if (row.sub_part_detail.assembly) { - html += makeIconButton('fa-tools', 'button-build', pk, '{% trans "Build parts" %}'); - } - - html += makeIconButton('fa-plus icon-green', 'button-add', pk, '{% trans "Allocate stock" %}'); - {% endif %} - - html += '
'; - - return html; - }, + {% for item in build.incomplete_outputs %} + // Get the build output as a javascript object + inventreeGet('{% url 'api-stock-detail' item.pk %}', {}, + { + success: function(response) { + loadBuildOutputAllocationTable(buildInfo, response); } - {% endif %} - ], - }); + } + ); + {% endfor %} {% if build.status == BuildStatus.PENDING %} $("#btn-allocate").on('click', function() { @@ -424,7 +88,15 @@ InvenTree | Allocate Parts launchModalForm( "{% url 'build-unallocate' build.id %}", { - success: reloadTable, + reload: true, + } + ); + }); + + $('#btn-create-output').click(function() { + launchModalForm('{% url "build-output-create" build.id %}', + { + reload: true, } ); }); diff --git a/InvenTree/build/templates/build/allocation_card.html b/InvenTree/build/templates/build/allocation_card.html new file mode 100644 index 0000000000..650257bb0d --- /dev/null +++ b/InvenTree/build/templates/build/allocation_card.html @@ -0,0 +1,43 @@ +{% load i18n %} +{% load inventree_extras %} + +{% define item.pk as pk %} + + \ No newline at end of file diff --git a/InvenTree/build/templates/build/attachments.html b/InvenTree/build/templates/build/attachments.html new file mode 100644 index 0000000000..a2dde0868c --- /dev/null +++ b/InvenTree/build/templates/build/attachments.html @@ -0,0 +1,78 @@ +{% extends "build/build_base.html" %} + +{% load static %} +{% load i18n %} +{% load markdownify %} + +{% block details %} + +{% include "build/tabs.html" with tab='attachments' %} + +

{% trans "Attachments" %}

+
+ +{% include "attachment_table.html" with attachments=build.attachments.all %} + +{% endblock %} + +{% block js_ready %} +{{ block.super }} + +enableDragAndDrop( + '#attachment-dropzone', + '{% url "build-attachment-create" %}', + { + data: { + build: {{ build.id }}, + }, + label: 'attachment', + success: function(data, status, xhr) { + location.reload(); + } + } +); + +// Callback for creating a new attachment +$('#new-attachment').click(function() { + launchModalForm( + '{% url "build-attachment-create" %}', + { + reload: true, + data: { + build: {{ build.pk }}, + } + } + ); +}); + +// Callback for editing an attachment +$("#attachment-table").on('click', '.attachment-edit-button', function() { + var pk = $(this).attr('pk'); + + var url = `/build/attachment/${pk}/edit/`; + + launchModalForm( + url, + { + reload: true, + } + ); +}); + +// Callback for deleting an attachment +$("#attachment-table").on('click', '.attachment-delete-button', function() { + var pk = $(this).attr('pk'); + + var url = `/build/attachment/${pk}/delete/`; + + launchModalForm( + url, + { + reload: true, + } + ); +}); + +$("#attachment-table").inventreeTable({}); + +{% endblock %} diff --git a/InvenTree/build/templates/build/auto_allocate.html b/InvenTree/build/templates/build/auto_allocate.html index 241471e304..48d1837ae0 100644 --- a/InvenTree/build/templates/build/auto_allocate.html +++ b/InvenTree/build/templates/build/auto_allocate.html @@ -6,13 +6,10 @@ {{ block.super }}
-{% trans "Automatically Allocate Stock" %}
-{% trans "Stock Items are selected for automatic allocation if there is only a single stock item available." %}
-{% trans "The following stock items will be allocated to the build:" %}
+ {% trans "Automatically Allocate Stock" %}
+ {% trans "The following stock items will be allocated to the specified build output" %}
- {% if allocations %} - diff --git a/InvenTree/build/templates/build/build_base.html b/InvenTree/build/templates/build/build_base.html index a366459908..3a94398e87 100644 --- a/InvenTree/build/templates/build/build_base.html +++ b/InvenTree/build/templates/build/build_base.html @@ -68,11 +68,6 @@ src="{% static 'img/blank_image.png' %}"

{% trans "Build Details" %}

- - - - - @@ -88,6 +83,11 @@ src="{% static 'img/blank_image.png' %}" + + + + + {% if build.parent %} @@ -102,20 +102,6 @@ src="{% static 'img/blank_image.png' %}" {% endif %} - - - - -
{% trans "Build Order Reference" %}{{ build }}
{% trans "Part" %}{% trans "Status" %} {% build_status_label build.status %}
{% trans "Progress" %} {{ build.completed }} / {{ build.quantity }}
{{ build.sales_order }}
{% trans "BOM Price" %} - {% if bom_price %} - {{ bom_price }} - {% if build.part.has_complete_bom_pricing == False %} -
{% trans "BOM pricing is incomplete" %} - {% endif %} - {% else %} - {% trans "No pricing information" %} - {% endif %} -
{% endblock %} diff --git a/InvenTree/build/templates/build/build_output_create.html b/InvenTree/build/templates/build/build_output_create.html new file mode 100644 index 0000000000..5de41695a6 --- /dev/null +++ b/InvenTree/build/templates/build/build_output_create.html @@ -0,0 +1,20 @@ +{% extends "modal_form.html" %} +{% load i18n %} +{% block pre_form_content %} + +{% if build.part.has_trackable_parts %} +
+ {% trans "The Bill of Materials contains trackable parts" %}
+ {% trans "Build outputs must be generated individually." %}
+ {% trans "Multiple build outputs will be created based on the quantity specified." %} +
+{% endif %} + +{% if build.part.trackable %} +
+ {% trans "Trackable parts can have serial numbers specified" %}
+ {% trans "Enter serial numbers to generate multiple single build outputs" %} +
+{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/build/templates/build/cancel.html b/InvenTree/build/templates/build/cancel.html index d7e4d51b10..48d8ca09bd 100644 --- a/InvenTree/build/templates/build/cancel.html +++ b/InvenTree/build/templates/build/cancel.html @@ -1,7 +1,7 @@ {% extends "modal_form.html" %} - +{% load i18n %} {% block pre_form_content %} -Are you sure you wish to cancel this build? +{% trans "Are you sure you wish to cancel this build?" %} {% endblock %} \ No newline at end of file diff --git a/InvenTree/build/templates/build/complete.html b/InvenTree/build/templates/build/complete.html index a48b831645..0db6a336a5 100644 --- a/InvenTree/build/templates/build/complete.html +++ b/InvenTree/build/templates/build/complete.html @@ -3,35 +3,21 @@ {% block pre_form_content %} -

{% trans "Build" %} - {{ build }}

- -{% if build.isFullyAllocated %} -
-

{% trans "Build order allocation is complete" %}

+{% if build.can_complete %} +
+ {% trans "Build can be completed" %}
{% else %}
-

{% trans "Warning: Build order allocation is not complete" %}

- {% trans "Build Order has not been fully allocated. Ensure that all Stock Items have been allocated to the Build" %} -
-{% endif %} - -
-

{% trans "The following actions will be performed:" %}

+ {% trans "Build cannot be completed" %}
    -
  • {% trans "Remove allocated items from stock" %}
  • -
  • {% trans "Add completed items to stock" %}
  • + {% if build.incomplete_count > 0 %} +
  • {% trans "Incompleted build outputs remain" %}
  • + {% endif %} + {% if build.completed < build.quantity %} +
  • {% trans "Required build quantity has not been completed" %}
  • + {% endif %}
- -
-
- {% trans "The following items will be created" %} -
-
- {% include "hover_image.html" with image=build.part.image hover=True %} - {{ build.quantity }} x {{ build.part.full_name }} -
-
- +{% endif %} {% endblock %} \ No newline at end of file diff --git a/InvenTree/build/templates/build/complete_output.html b/InvenTree/build/templates/build/complete_output.html new file mode 100644 index 0000000000..54c3fc6763 --- /dev/null +++ b/InvenTree/build/templates/build/complete_output.html @@ -0,0 +1,48 @@ +{% extends "modal_form.html" %} +{% load inventree_extras %} +{% load i18n %} + +{% block pre_form_content %} + +{% if fully_allocated %} +
+

{% trans "Stock allocation is complete" %}

+
+{% else %} +
+

{% trans "Stock allocation is incomplete" %}

+ +
+
+ +
+
+
    + {% for part in unallocated_parts %} +
  • + {% include "hover_image.html" with image=part.image %} {{ part }} +
  • + {% endfor %} +
+
+
+
+
+
+{% endif %} + +
+
+ {% trans "The following items will be created" %} +
+
+ {% include "hover_image.html" with image=build.part.image hover=True %} + {% decimal output.quantity %} x {{ output.part.full_name }} +
+
+ +{% endblock %} \ No newline at end of file diff --git a/InvenTree/build/templates/build/create_build_item.html b/InvenTree/build/templates/build/create_build_item.html index c08b987d36..8f58e884d6 100644 --- a/InvenTree/build/templates/build/create_build_item.html +++ b/InvenTree/build/templates/build/create_build_item.html @@ -1,9 +1,22 @@ {% extends "modal_form.html" %} +{% load i18n %} {% block pre_form_content %} +
+

+ {% trans "Select a stock item to allocate to the selected build output" %} +

+ {% if output %} +

+ {% trans "The allocated stock will be installed into the following build output:" %} +
+ {{ output }} +

+ {% endif %} +
{% if no_stock %} {% endif %} {% endblock %} \ No newline at end of file diff --git a/InvenTree/build/templates/build/delete_build_item.html b/InvenTree/build/templates/build/delete_build_item.html index f9c41b9eb5..d5cc285466 100644 --- a/InvenTree/build/templates/build/delete_build_item.html +++ b/InvenTree/build/templates/build/delete_build_item.html @@ -3,7 +3,12 @@ {% load inventree_extras %} {% block pre_form_content %} -{% trans "Are you sure you want to unallocate these parts?" %} -
-This will remove {% decimal item.quantity %} parts from build '{{ item.build.title }}'. +
+

+ {% trans "Are you sure you want to unallocate this stock?" %} +

+

+ {% trans "The selected stock will be unallocated from the build output" %} +

+
{% endblock %} \ No newline at end of file diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html index 6abbc69bc5..68a842755d 100644 --- a/InvenTree/build/templates/build/detail.html +++ b/InvenTree/build/templates/build/detail.html @@ -10,77 +10,120 @@
- - - - - - - - - - - - - - - - - - - - + + + + + {% endif %} + {% if build.link %} + + + + + + {% endif %} + + + + + +
{% trans "Title" %}{{ build.title }}
{% trans "Part" %}{{ build.part.full_name }}
{% trans "Quantity" %}{{ build.quantity }}
{% trans "Stock Source" %} - {% if build.take_from %} - {{ build.take_from }} - {% else %} - {% trans "Stock can be taken from any available location." %} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if build.batch %} + + + + + {% endif %} - - - - - - - -{% if build.batch %} - - - - - -{% endif %} -{% if build.link %} - - - - - -{% endif %} - - - - - -{% if build.is_active %} - - - - + + + + {% endif %} - - -{% endif %} -{% if build.completion_date %} - - - - - -{% endif %} -
{% trans "Description" %}{{ build.title }}
{% trans "Part" %}{{ build.part.full_name }}
{% trans "Quantity" %}{{ build.quantity }}
{% trans "Stock Source" %} + {% if build.take_from %} + {{ build.take_from }} + {% else %} + {% trans "Stock can be taken from any available location." %} + {% endif %} +
{% trans "Destination" %} + {% if build.destination %} + + {{ build.destination }} + + {% else %} + {% trans "Destination location not specified" %} + {% endif %} +
{% trans "Status" %}{% build_status_label build.status %}
{% trans "Progress" %}{{ build.completed }} / {{ build.quantity }}
{% trans "Batch" %}{{ build.batch }}
{% trans "Status" %}{% build_status_label build.status %}
{% trans "Batch" %}{{ build.batch }}
{% trans "External Link" %}{{ build.link }}
{% trans "Created" %}{{ build.creation_date }}
{% trans "Enough Parts?" %} - {% if build.can_build %} - {% trans "Yes" %} - {% else %} - {% trans "No" %} + {% if build.parent %} +
{% trans "Parent Build" %}{{ build.parent }}
{% trans "Completed" %}{{ build.completion_date }}{% if build.completed_by %}{{ build.completed_by }}{% endif %}
+ {% if build.sales_order %} +
{% trans "Sales Order" %}{{ build.sales_order }}
{% trans "External Link" %}{{ build.link }}
{% trans "Created" %}{{ build.creation_date }}
+
+
+ + + + + + + + {% if build.completion_date %} + + + + + + {% endif %} + +
{% trans "BOM Price" %} + {% if bom_price %} + {{ bom_price }} + {% if build.part.has_complete_bom_pricing == False %} +
{% trans "BOM pricing is incomplete" %} + {% endif %} + {% else %} + {% trans "No pricing information" %} + {% endif %} +
{% trans "Completed" %}{{ build.completion_date }}{% if build.completed_by %}{{ build.completed_by }}{% endif %}
+
+
{% endblock %} diff --git a/InvenTree/build/templates/build/edit_build_item.html b/InvenTree/build/templates/build/edit_build_item.html new file mode 100644 index 0000000000..99cad71ba2 --- /dev/null +++ b/InvenTree/build/templates/build/edit_build_item.html @@ -0,0 +1,10 @@ +{% extends "modal_form.html" %} +{% load i18n %} + +{% block pre_form_content %} +
+

+ {% trans "Alter the quantity of stock allocated to the build output" %} +

+
+{% endblock %} \ No newline at end of file diff --git a/InvenTree/build/templates/build/index.html b/InvenTree/build/templates/build/index.html index ca21b9abf3..562dc746fb 100644 --- a/InvenTree/build/templates/build/index.html +++ b/InvenTree/build/templates/build/index.html @@ -41,12 +41,7 @@ InvenTree | {% trans "Build Orders" %} $("#collapse-item-active").collapse().show(); $("#new-build").click(function() { - launchModalForm( - "{% url 'build-create' %}", - { - follow: true - } - ); + newBuildOrder(); }); loadBuildTable($("#build-table"), { diff --git a/InvenTree/build/templates/build/tabs.html b/InvenTree/build/templates/build/tabs.html index ca2e92f290..2c45cd2361 100644 --- a/InvenTree/build/templates/build/tabs.html +++ b/InvenTree/build/templates/build/tabs.html @@ -5,7 +5,7 @@ {% trans "Details" %} - {% trans "Allocated Parts" %} + {% trans "Allocate Parts" %} {% trans "Build Outputs" %}{% if build.output_count > 0%}{{ build.output_count }}{% endif %} @@ -13,4 +13,7 @@ {% trans "Notes" %}{% if build.notes %} {% endif %} +
  • + {% trans "Attachments" %} +
  • \ No newline at end of file diff --git a/InvenTree/build/templates/build/unallocate.html b/InvenTree/build/templates/build/unallocate.html index f6543b3732..a650e95718 100644 --- a/InvenTree/build/templates/build/unallocate.html +++ b/InvenTree/build/templates/build/unallocate.html @@ -5,6 +5,11 @@ {{ block.super }} -{% trans "Are you sure you wish to unallocate all stock for this build?" %} + +
    + {% trans "Are you sure you wish to unallocate all stock for this build?" %} +
    + {% trans "All incomplete stock allocations will be removed from the build" %} +
    {% endblock %} \ No newline at end of file diff --git a/InvenTree/build/test_build.py b/InvenTree/build/test_build.py index e69853c269..b560a4f9c9 100644 --- a/InvenTree/build/test_build.py +++ b/InvenTree/build/test_build.py @@ -3,7 +3,6 @@ from django.test import TestCase from django.core.exceptions import ValidationError -from django.db import transaction from django.db.utils import IntegrityError from build.models import Build, BuildItem @@ -11,8 +10,6 @@ from stock.models import StockItem from part.models import Part, BomItem from InvenTree import status_codes as status -from InvenTree.helpers import ExtractSerialNumbers - class BuildTest(TestCase): """ @@ -64,6 +61,21 @@ class BuildTest(TestCase): quantity=10 ) + # Create some build output (StockItem) objects + self.output_1 = StockItem.objects.create( + part=self.assembly, + quantity=5, + is_building=True, + build=self.build + ) + + self.output_2 = StockItem.objects.create( + part=self.assembly, + quantity=5, + is_building=True, + build=self.build, + ) + # Create some stock items to assign to the build self.stock_1_1 = StockItem.objects.create(part=self.sub_part_1, quantity=1000) self.stock_1_2 = StockItem.objects.create(part=self.sub_part_1, quantity=100) @@ -73,23 +85,28 @@ class BuildTest(TestCase): def test_init(self): # Perform some basic tests before we start the ball rolling - self.assertEqual(StockItem.objects.count(), 3) + self.assertEqual(StockItem.objects.count(), 5) + + # Build is PENDING self.assertEqual(self.build.status, status.BuildStatus.PENDING) - self.assertFalse(self.build.isFullyAllocated()) - self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_1)) - self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_2)) + # Build has two build outputs + self.assertEqual(self.build.output_count, 2) - self.assertEqual(self.build.getRequiredQuantity(self.sub_part_1), 100) - self.assertEqual(self.build.getRequiredQuantity(self.sub_part_2), 250) + # None of the build outputs have been completed + for output in self.build.get_build_outputs().all(): + self.assertFalse(self.build.isFullyAllocated(output)) + + self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_1, self.output_1)) + self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_2, self.output_2)) + + self.assertEqual(self.build.unallocatedQuantity(self.sub_part_1, self.output_1), 50) + self.assertEqual(self.build.unallocatedQuantity(self.sub_part_1, self.output_2), 50) + self.assertEqual(self.build.unallocatedQuantity(self.sub_part_2, self.output_1), 125) + self.assertEqual(self.build.unallocatedQuantity(self.sub_part_2, self.output_2), 125) - self.assertTrue(self.build.can_build) self.assertFalse(self.build.is_complete) - # Delete some stock and see if the build can still be completed - self.stock_2_1.delete() - self.assertFalse(self.build.can_build) - def test_build_item_clean(self): # Ensure that dodgy BuildItem objects cannot be created @@ -99,7 +116,7 @@ class BuildTest(TestCase): b = BuildItem(stock_item=stock, build=self.build, quantity=10) with self.assertRaises(ValidationError): - b.clean() + b.save() # Create a BuildItem which has too much stock assigned b = BuildItem(stock_item=self.stock_1_1, build=self.build, quantity=9999999) @@ -113,6 +130,10 @@ class BuildTest(TestCase): with self.assertRaises(ValidationError): b.clean() + # Ok, what about we make one that does *not* fail? + b = BuildItem(stock_item=self.stock_1_1, build=self.build, install_into=self.output_1, quantity=10) + b.save() + def test_duplicate_bom_line(self): # Try to add a duplicate BOM item - it should fail! @@ -123,105 +144,145 @@ class BuildTest(TestCase): quantity=99 ) - def allocate_stock(self, q11, q12, q21): + def allocate_stock(self, q11, q12, q21, output): # Assign stock to this build - BuildItem.objects.create( - build=self.build, - stock_item=self.stock_1_1, - quantity=q11 - ) + if q11 > 0: + BuildItem.objects.create( + build=self.build, + stock_item=self.stock_1_1, + quantity=q11, + install_into=output + ) - BuildItem.objects.create( - build=self.build, - stock_item=self.stock_1_2, - quantity=q12 - ) + if q12 > 0: + BuildItem.objects.create( + build=self.build, + stock_item=self.stock_1_2, + quantity=q12, + install_into=output + ) - BuildItem.objects.create( - build=self.build, - stock_item=self.stock_2_1, - quantity=q21 - ) + if q21 > 0: + BuildItem.objects.create( + build=self.build, + stock_item=self.stock_2_1, + quantity=q21, + install_into=output, + ) - with transaction.atomic(): - with self.assertRaises(IntegrityError): - BuildItem.objects.create( - build=self.build, - stock_item=self.stock_2_1, - quantity=99 - ) + # Attempt to create another identical BuildItem + b = BuildItem( + build=self.build, + stock_item=self.stock_2_1, + quantity=q21 + ) - self.assertEqual(BuildItem.objects.count(), 3) + with self.assertRaises(ValidationError): + b.clean() def test_partial_allocation(self): + """ + Partially allocate against output 1 + """ - self.allocate_stock(50, 50, 200) + self.allocate_stock(50, 50, 200, self.output_1) - self.assertFalse(self.build.isFullyAllocated()) - self.assertTrue(self.build.isPartFullyAllocated(self.sub_part_1)) - self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_2)) + self.assertTrue(self.build.isFullyAllocated(self.output_1)) + self.assertFalse(self.build.isFullyAllocated(self.output_2)) + self.assertTrue(self.build.isPartFullyAllocated(self.sub_part_1, self.output_1)) + self.assertTrue(self.build.isPartFullyAllocated(self.sub_part_2, self.output_1)) + + self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_1, self.output_2)) + self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_2, self.output_2)) - self.build.unallocateStock() + # Check that the part has been allocated + self.assertEqual(self.build.allocatedQuantity(self.sub_part_1, self.output_1), 100) + + self.build.unallocateStock(output=self.output_1) self.assertEqual(BuildItem.objects.count(), 0) - def test_auto_allocate(self): + # Check that the part has been unallocated + self.assertEqual(self.build.allocatedQuantity(self.sub_part_1, self.output_1), 0) - allocations = self.build.getAutoAllocations() + def test_auto_allocate(self): + """ + Test auto-allocation functionality against the build outputs + """ + + allocations = self.build.getAutoAllocations(self.output_1) self.assertEqual(len(allocations), 1) - self.build.autoAllocate() + self.build.autoAllocate(self.output_1) self.assertEqual(BuildItem.objects.count(), 1) - self.assertTrue(self.build.isPartFullyAllocated(self.sub_part_2)) + + # Check that one part has been fully allocated to the build output + self.assertTrue(self.build.isPartFullyAllocated(self.sub_part_2, self.output_1)) + + # But, the *other* build output has not been allocated against + self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_2, self.output_2)) def test_cancel(self): + """ + Test cancellation of the build + """ - self.allocate_stock(50, 50, 200) + # TODO + + """ + self.allocate_stock(50, 50, 200, self.output_1) self.build.cancelBuild(None) self.assertEqual(BuildItem.objects.count(), 0) + """ + pass def test_complete(self): + """ + Test completion of a build output + """ - self.allocate_stock(50, 50, 250) + self.allocate_stock(50, 50, 250, self.output_1) + self.allocate_stock(50, 50, 250, self.output_2) - self.assertTrue(self.build.isFullyAllocated()) + self.assertTrue(self.build.isFullyAllocated(self.output_1)) + self.assertTrue(self.build.isFullyAllocated(self.output_2)) - # Generate some serial numbers! - serials = ExtractSerialNumbers("1-10", 10) + self.build.completeBuildOutput(self.output_1, None) - self.build.completeBuild(None, serials, None) + self.assertFalse(self.build.can_complete) + self.build.completeBuildOutput(self.output_2, None) + + self.assertTrue(self.build.can_complete) + + self.build.complete_build(None) + self.assertEqual(self.build.status, status.BuildStatus.COMPLETE) # the original BuildItem objects should have been deleted! self.assertEqual(BuildItem.objects.count(), 0) # New stock items should have been created! - # - Ten for the build output (as the part was serialized) - # - Three for the split items assigned to the build - self.assertEqual(StockItem.objects.count(), 16) + self.assertEqual(StockItem.objects.count(), 4) A = StockItem.objects.get(pk=self.stock_1_1.pk) - B = StockItem.objects.get(pk=self.stock_1_2.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) # Stock should have been subtracted from the original items - self.assertEqual(A.quantity, 950) - self.assertEqual(B.quantity, 50) - self.assertEqual(C.quantity, 4750) - - # New stock items should have also been allocated to the build - allocated = StockItem.objects.filter(build_order=self.build) - - self.assertEqual(allocated.count(), 3) - - q = sum([item.quantity for item in allocated.all()]) - - self.assertEqual(q, 350) + 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) - self.assertEqual(outputs.count(), 10) + self.assertEqual(outputs.count(), 2) + + for output in outputs: + self.assertFalse(output.is_building) diff --git a/InvenTree/build/tests.py b/InvenTree/build/tests.py index ded98a441c..f01aaa83c9 100644 --- a/InvenTree/build/tests.py +++ b/InvenTree/build/tests.py @@ -12,6 +12,7 @@ from rest_framework import status import json from .models import Build +from stock.models import StockItem from InvenTree.status_codes import BuildStatus @@ -49,7 +50,7 @@ class BuildTestSimple(TestCase): def test_build_objects(self): # Ensure the Build objects were correctly created - self.assertEqual(Build.objects.count(), 2) + self.assertEqual(Build.objects.count(), 5) b = Build.objects.get(pk=2) self.assertEqual(b.batch, 'B2') self.assertEqual(b.quantity, 21) @@ -127,11 +128,37 @@ class TestBuildAPI(APITestCase): self.client.login(username='testuser', password='password') def test_get_build_list(self): - """ Test that we can retrieve list of build objects """ + """ + Test that we can retrieve list of build objects + """ + url = reverse('api-build-list') response = self.client.get(url, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 5) + + # Filter query by build status + response = self.client.get(url, {'status': 40}, format='json') + + self.assertEqual(len(response.data), 4) + + # Filter by "active" status + response = self.client.get(url, {'active': True}, format='json') + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]['pk'], 1) + + response = self.client.get(url, {'active': False}, format='json') + self.assertEqual(len(response.data), 4) + + # Filter by 'part' status + response = self.client.get(url, {'part': 25}, format='json') + self.assertEqual(len(response.data), 2) + + # Filter by an invalid part + response = self.client.get(url, {'part': 99999}, format='json') + self.assertEqual(len(response.data), 0) + def test_get_build_item_list(self): """ Test that we can retrieve list of BuildItem objects """ url = reverse('api-build-item-list') @@ -176,6 +203,16 @@ class TestBuildViews(TestCase): self.client.login(username='username', password='password') + # Create a build output for build # 1 + self.build = Build.objects.get(pk=1) + + self.output = StockItem.objects.create( + part=self.build.part, + quantity=self.build.quantity, + build=self.build, + is_building=True, + ) + def test_build_index(self): """ test build index view """ @@ -254,10 +291,15 @@ class TestBuildViews(TestCase): # url = reverse('build-item-edit') pass - def test_build_complete(self): - """ Test the build completion form """ + def test_build_output_complete(self): + """ + Test the build output completion form + """ - url = reverse('build-complete', args=(1,)) + # Firstly, check that the build cannot be completed! + self.assertFalse(self.build.can_complete) + + url = reverse('build-output-complete', args=(1,)) # Test without confirmation response = self.client.post(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') @@ -267,12 +309,26 @@ class TestBuildViews(TestCase): self.assertFalse(data['form_valid']) # Test with confirmation, valid location - response = self.client.post(url, {'confirm': 1, 'location': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.post( + url, + { + 'confirm': 1, + 'confirm_incomplete': 1, + 'location': 1, + 'output': self.output.pk, + }, + HTTP_X_REQUESTED_WITH='XMLHttpRequest' + ) + self.assertEqual(response.status_code, 200) - + data = json.loads(response.content) self.assertTrue(data['form_valid']) + # Now the build should be able to be completed + self.build.refresh_from_db() + self.assertTrue(self.build.can_complete) + # Test with confirmation, invalid location response = self.client.post(url, {'confirm': 1, 'location': 9999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(response.status_code, 200) diff --git a/InvenTree/build/urls.py b/InvenTree/build/urls.py index d8cb3c03ea..08142e6939 100644 --- a/InvenTree/build/urls.py +++ b/InvenTree/build/urls.py @@ -11,12 +11,16 @@ build_detail_urls = [ url(r'^allocate/', views.BuildAllocate.as_view(), name='build-allocate'), url(r'^cancel/', views.BuildCancel.as_view(), name='build-cancel'), url(r'^delete/', views.BuildDelete.as_view(), name='build-delete'), - url(r'^complete/?', views.BuildComplete.as_view(), name='build-complete'), + url(r'^create-output/', views.BuildOutputCreate.as_view(), name='build-output-create'), + url(r'^delete-output/', views.BuildOutputDelete.as_view(), name='build-output-delete'), + url(r'^complete-output/?', views.BuildOutputComplete.as_view(), name='build-output-complete'), url(r'^auto-allocate/?', views.BuildAutoAllocate.as_view(), name='build-auto-allocate'), url(r'^unallocate/', views.BuildUnallocate.as_view(), name='build-unallocate'), + url(r'^complete/', views.BuildComplete.as_view(), name='build-complete'), url(r'^notes/', views.BuildNotes.as_view(), name='build-notes'), + url(r'^attachments/', views.BuildDetail.as_view(template_name='build/attachments.html'), name='build-attachments'), url(r'^output/', views.BuildDetail.as_view(template_name='build/build_output.html'), name='build-output'), url(r'^.*$', views.BuildDetail.as_view(), name='build-detail'), @@ -31,6 +35,12 @@ build_urls = [ url('^new/', views.BuildItemCreate.as_view(), name='build-item-create'), ])), + url('^attachment/', include([ + url('^new/', views.BuildAttachmentCreate.as_view(), name='build-attachment-create'), + url(r'^(?P\d+)/edit/', views.BuildAttachmentEdit.as_view(), name='build-attachment-edit'), + url(r'^(?P\d+)/delete/', views.BuildAttachmentDelete.as_view(), name='build-attachment-delete'), + ])), + url(r'new/', views.BuildCreate.as_view(), name='build-create'), url(r'^(?P\d+)/', include(build_detail_urls)), diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 1a2bd0d4b7..5dd36a6871 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -12,13 +12,13 @@ from django.forms import HiddenInput from django.urls import reverse from part.models import Part -from .models import Build, BuildItem +from .models import Build, BuildItem, BuildOrderAttachment from . import forms from stock.models import StockLocation, StockItem from InvenTree.views import AjaxUpdateView, AjaxCreateView, AjaxDeleteView from InvenTree.views import InvenTreeRoleMixin -from InvenTree.helpers import str2bool, ExtractSerialNumbers +from InvenTree.helpers import str2bool, extract_serial_numbers, normalize from InvenTree.status_codes import BuildStatus @@ -67,7 +67,7 @@ class BuildCancel(AjaxUpdateView): if not confirm: form.add_error('confirm_cancel', _('Confirm build cancellation')) - def post_save(self, build, form, **kwargs): + def save(self, build, form, **kwargs): """ Cancel the build. """ @@ -88,52 +88,246 @@ class BuildAutoAllocate(AjaxUpdateView): """ model = Build - form_class = forms.ConfirmBuildForm + form_class = forms.AutoAllocateForm context_object_name = 'build' ajax_form_title = _('Allocate Stock') ajax_template_name = 'build/auto_allocate.html' role_required = 'build.change' + def get_initial(self): + """ + Initial values for the form. + """ + + initials = super().get_initial() + + # Pointing to a particular build output? + output = self.get_param('output') + + if output: + try: + output = StockItem.objects.get(pk=output) + initials['output'] = output + except (ValueError, StockItem.DoesNotExist): + pass + + return initials + def get_context_data(self, *args, **kwargs): - """ Get the context data for form rendering. """ + """ + Get the context data for form rendering. + """ context = {} + build = self.get_object() + + form = self.get_form() + + output_id = form['output'].value() + try: - build = Build.objects.get(id=self.kwargs['pk']) - context['build'] = build - context['allocations'] = build.getAutoAllocations() - except Build.DoesNotExist: - context['error'] = _('No matching build found') + output = StockItem.objects.get(pk=output_id) + except (ValueError, StockItem.DoesNotExist): + output = None + + if output: + context['output'] = output + context['allocations'] = build.getAutoAllocations(output) + + context['build'] = build return context - def post(self, request, *args, **kwargs): - """ Handle POST request. Perform auto allocations. + def get_form(self): - - If the form validation passes, perform allocations - - Otherwise, the form is passed back to the client + form = super().get_form() + + if form['output'].value(): + # Hide the 'output' field + form.fields['output'].widget = HiddenInput() + + return form + + def validate(self, build, form, **kwargs): + + output = form.cleaned_data.get('output', None) + + if not output: + form.add_error(None, _('Build output must be specified')) + + def save(self, build, form, **kwargs): + """ + Once the form has been validated, + perform auto-allocations """ - build = self.get_object() - form = self.get_form() + output = form.cleaned_data.get('output', None) - confirm = request.POST.get('confirm', False) + build.autoAllocate(output) - valid = False - - if confirm is False: - form.add_error('confirm', _('Confirm stock allocation')) - form.add_error(None, _('Check the confirmation box at the bottom of the list')) - else: - build.autoAllocate() - valid = True - - data = { - 'form_valid': valid, + def get_data(self): + return { + 'success': _('Allocated stock to build output'), } - return self.renderJsonResponse(request, form, data, context=self.get_context_data()) + +class BuildOutputCreate(AjaxUpdateView): + """ + Create a new build output (StockItem) for a given build. + """ + + model = Build + form_class = forms.BuildOutputCreateForm + ajax_template_name = 'build/build_output_create.html' + ajax_form_title = _('Create Build Output') + role_required = 'build.change' + + def validate(self, build, form, **kwargs): + """ + Validation for the form: + """ + + quantity = form.cleaned_data.get('quantity', None) + serials = form.cleaned_data.get('serial_numbers', None) + + # Check that the serial numbers are valid + if serials: + try: + extracted = extract_serial_numbers(serials, quantity) + + if extracted: + # Check for conflicting serial numbers + conflicts = build.part.find_conflicting_serial_numbers(extracted) + + if len(conflicts) > 0: + msg = ",".join([str(c) for c in conflicts]) + form.add_error( + 'serial_numbers', + _('Serial numbers already exist') + ': ' + msg, + ) + + except ValidationError as e: + form.add_error('serial_numbers', e.messages) + + else: + # If no serial numbers are provided, should they be? + if build.part.trackable: + form.add_error('serial_numbers', _('Serial numbers required for trackable build output')) + + def save(self, build, form, **kwargs): + """ + Create a new build output + """ + + data = form.cleaned_data + + quantity = data.get('quantity', None) + batch = data.get('batch', None) + + serials = data.get('serial_numbers', None) + + if serials: + serial_numbers = extract_serial_numbers(serials, quantity) + else: + serial_numbers = None + + build.create_build_output( + quantity, + serials=serial_numbers, + batch=batch, + ) + + def get_initial(self): + + initials = super().get_initial() + + build = self.get_object() + + # Calculate the required quantity + quantity = max(0, build.remaining - build.incomplete_count) + initials['quantity'] = quantity + + return initials + + def get_form(self): + + build = self.get_object() + part = build.part + + context = self.get_form_kwargs() + + # Pass the 'part' through to the form, + # so we can add the next serial number as a placeholder + context['build'] = build + + form = self.form_class(**context) + + # If the part is not trackable, hide the serial number input + if not part.trackable: + form.fields['serial_numbers'].widget = HiddenInput() + + return form + + +class BuildOutputDelete(AjaxUpdateView): + """ + Delete a build output (StockItem) for a given build. + + Form is a simple confirmation dialog + """ + + model = Build + form_class = forms.BuildOutputDeleteForm + ajax_form_title = _('Delete Build Output') + role_required = 'build.delete' + + def get_initial(self): + + initials = super().get_initial() + + output = self.get_param('output') + + initials['output_id'] = output + + return initials + + def validate(self, build, form, **kwargs): + + data = form.cleaned_data + + confirm = data.get('confirm', False) + + if not confirm: + form.add_error('confirm', _('Confirm unallocation of build stock')) + form.add_error(None, _('Check the confirmation box')) + + output_id = data.get('output_id', None) + output = None + + try: + output = StockItem.objects.get(pk=output_id) + except (ValueError, StockItem.DoesNotExist): + pass + + if output: + if not output.build == build: + form.add_error(None, _('Build output does not match build')) + else: + form.add_error(None, _('Build output must be specified')) + + def save(self, build, form, **kwargs): + + output_id = form.cleaned_data.get('output_id') + + output = StockItem.objects.get(pk=output_id) + + build.deleteBuildOutput(output) + + def get_data(self): + return { + 'danger': _('Build output deleted'), + } class BuildUnallocate(AjaxUpdateView): @@ -143,10 +337,28 @@ class BuildUnallocate(AjaxUpdateView): """ model = Build - form_class = forms.ConfirmBuildForm + form_class = forms.UnallocateBuildForm ajax_form_title = _("Unallocate Stock") ajax_template_name = "build/unallocate.html" - form_required = 'build.change' + role_required = 'build.change' + + def get_initial(self): + + initials = super().get_initial() + + # Pointing to a particular build output? + output = self.get_param('output') + + if output: + initials['output_id'] = output + + # Pointing to a particular part? + part = self.get_param('part') + + if part: + initials['part_id'] = part + + return initials def post(self, request, *args, **kwargs): @@ -155,13 +367,27 @@ class BuildUnallocate(AjaxUpdateView): confirm = request.POST.get('confirm', False) + output_id = request.POST.get('output_id', None) + + try: + output = StockItem.objects.get(pk=output_id) + except (ValueError, StockItem.DoesNotExist): + output = None + + part_id = request.POST.get('part_id', None) + + try: + part = Part.objects.get(pk=part_id) + except (ValueError, Part.DoesNotExist): + part = None + valid = False if confirm is False: form.add_error('confirm', _('Confirm unallocation of build stock')) form.add_error(None, _('Check the confirmation box')) else: - build.unallocateStock() + build.unallocateStock(output=output, part=part) valid = True data = { @@ -172,7 +398,41 @@ class BuildUnallocate(AjaxUpdateView): class BuildComplete(AjaxUpdateView): - """ View to mark a build as Complete. + """ + View to mark the build as complete. + + Requirements: + - There can be no outstanding build outputs + - The "completed" value must meet or exceed the "quantity" value + """ + + model = Build + form_class = forms.CompleteBuildForm + role_required = 'build.change' + ajax_form_title = _('Complete Build Order') + ajax_template_name = 'build/complete.html' + + def validate(self, build, form, **kwargs): + + if not build.can_complete: + form.add_error(None, _('Build order cannot be completed')) + + def save(self, build, form, **kwargs): + """ + Perform the build completion step + """ + + build.complete_build(self.request.user) + + def get_data(self): + return { + 'success': _('Completed build order') + } + + +class BuildOutputComplete(AjaxUpdateView): + """ + View to mark a particular build output as Complete. - Notifies the user of which parts will be removed from stock. - Removes allocated items from stock @@ -180,39 +440,62 @@ class BuildComplete(AjaxUpdateView): """ model = Build - form_class = forms.CompleteBuildForm + form_class = forms.CompleteBuildOutputForm context_object_name = "build" - ajax_form_title = _("Complete Build") - ajax_template_name = "build/complete.html" + ajax_form_title = _("Complete Build Output") + ajax_template_name = "build/complete_output.html" role_required = 'build.change' def get_form(self): - """ Get the form object. - - If the part is trackable, include a field for serial numbers. - """ + build = self.get_object() form = super().get_form() - if not build.part.trackable: - form.fields.pop('serial_numbers') - else: + # Extract the build output object + output = None + output_id = form['output'].value() - form.field_placeholder['serial_numbers'] = build.part.getSerialNumberString(build.quantity) + try: + output = StockItem.objects.get(pk=output_id) + except (ValueError, StockItem.DoesNotExist): + pass - form.rebuild_layout() + if output: + if build.isFullyAllocated(output): + form.fields['confirm_incomplete'].widget = HiddenInput() return form + def validate(self, build, form, **kwargs): + + data = form.cleaned_data + + output = data.get('output', None) + + if output: + + quantity = data.get('quantity', None) + + if quantity and quantity > output.quantity: + form.add_error('quantity', _('Quantity to complete cannot exceed build output quantity')) + + if not build.isFullyAllocated(output): + confirm = str2bool(data.get('confirm_incomplete', False)) + + if not confirm: + form.add_error('confirm_incomplete', _('Confirm completion of incomplete build')) + + else: + form.add_error(None, _('Build output must be specified')) + def get_initial(self): """ Get initial form data for the CompleteBuild form - If the part being built has a default location, pre-select that location """ - initials = super(BuildComplete, self).get_initial().copy() - + initials = super().get_initial() build = self.get_object() if build.part.default_location is not None: @@ -222,94 +505,77 @@ class BuildComplete(AjaxUpdateView): except StockLocation.DoesNotExist: pass + output = self.get_param('output', None) + + if output: + try: + output = StockItem.objects.get(pk=output) + except (ValueError, StockItem.DoesNotExist): + output = None + + # Output has not been supplied? Try to "guess" + if not output: + + incomplete = build.get_build_outputs(complete=False) + + if incomplete.count() == 1: + output = incomplete[0] + + if output is not None: + initials['output'] = output + + initials['location'] = build.destination + return initials def get_context_data(self, **kwargs): - """ Get context data for passing to the rendered form + """ + Get context data for passing to the rendered form - Build information is required """ - build = Build.objects.get(id=self.kwargs['pk']) + build = self.get_object() context = {} # Build object context['build'] = build - # Items to be removed from stock - taking = BuildItem.objects.filter(build=build.id) - context['taking'] = taking - - return context - - def post(self, request, *args, **kwargs): - """ Handle POST request. Mark the build as COMPLETE - - - If the form validation passes, the Build objects completeBuild() method is called - - Otherwise, the form is passed back to the client - """ - - build = self.get_object() - form = self.get_form() - confirm = str2bool(request.POST.get('confirm', False)) + output = form['output'].value() - loc_id = request.POST.get('location', None) - - valid = False - - if confirm is False: - form.add_error('confirm', _('Confirm completion of build')) - else: + if output: try: - location = StockLocation.objects.get(id=loc_id) - valid = True - except (ValueError, StockLocation.DoesNotExist): - form.add_error('location', _('Invalid location selected')) + output = StockItem.objects.get(pk=output) + context['output'] = output + context['fully_allocated'] = build.isFullyAllocated(output) + context['allocated_parts'] = build.allocatedParts(output) + context['unallocated_parts'] = build.unallocatedParts(output) + except (ValueError, StockItem.DoesNotExist): + pass - serials = [] + return context - if build.part.trackable: - # A build for a trackable part may optionally specify serial numbers. + def save(self, build, form, **kwargs): - sn = request.POST.get('serial_numbers', '') + data = form.cleaned_data - sn = str(sn).strip() - - # If the user has specified serial numbers, check they are valid - if len(sn) > 0: - try: - # Exctract a list of provided serial numbers - serials = ExtractSerialNumbers(sn, build.quantity) - - existing = build.part.find_conflicting_serial_numbers(serials) - - if len(existing) > 0: - exists = ",".join([str(x) for x in existing]) - form.add_error('serial_numbers', _('The following serial numbers already exist: ({sn})'.format(sn=exists))) - valid = False - - except ValidationError as e: - form.add_error('serial_numbers', e.messages) - valid = False - - if valid: - if not build.completeBuild(location, serials, request.user): - form.add_error(None, _('Build could not be completed')) - valid = False - - data = { - 'form_valid': valid, - } - - return self.renderJsonResponse(request, form, data, context=self.get_context_data()) + location = data.get('location', None) + output = data.get('output', None) + # Complete the build output + build.completeBuildOutput( + output, + self.request.user, + location=location, + ) + def get_data(self): """ Provide feedback data back to the form """ return { - 'info': _('Build marked as COMPLETE') + 'success': _('Build output completed') } @@ -393,6 +659,14 @@ class BuildCreate(AjaxCreateView): ajax_template_name = 'modal_form.html' role_required = 'build.add' + def get_form(self): + form = super().get_form() + + if form['part'].value(): + form.fields['part'].widget = HiddenInput() + + return form + def get_initial(self): """ Get initial parameters for Build creation. @@ -401,8 +675,17 @@ class BuildCreate(AjaxCreateView): initials = super(BuildCreate, self).get_initial().copy() - # User has provided a Part ID - initials['part'] = self.request.GET.get('part', None) + part = self.request.GET.get('part', None) + + if part: + + try: + part = Part.objects.get(pk=part) + # User has provided a Part ID + initials['part'] = part + initials['destination'] = part.get_default_location() + except (ValueError, Part.DoesNotExist): + pass initials['reference'] = Build.getNextBuildNumber() @@ -420,6 +703,17 @@ class BuildCreate(AjaxCreateView): 'success': _('Created new build'), } + def validate(self, build, form, **kwargs): + """ + Perform extra form validation. + + - If part is trackable, check that either batch or serial numbers are calculated + + By this point form.is_valid() has been executed + """ + + pass + class BuildUpdate(AjaxUpdateView): """ View for editing a Build object """ @@ -431,6 +725,34 @@ class BuildUpdate(AjaxUpdateView): ajax_template_name = 'modal_form.html' role_required = 'build.change' + def get_form(self): + + form = super().get_form() + + build = self.get_object() + + # Fields which are included in the form, but hidden + hidden = [ + 'parent', + 'sales_order', + ] + + if build.is_complete: + # Fields which cannot be edited once the build has been completed + + hidden += [ + 'part', + 'quantity', + 'batch', + 'take_from', + 'destination', + ] + + for field in hidden: + form.fields[field].widget = HiddenInput() + + return form + def get_data(self): return { 'info': _('Edited build'), @@ -464,23 +786,37 @@ class BuildItemDelete(AjaxDeleteView): class BuildItemCreate(AjaxCreateView): - """ View for allocating a new part to a build """ + """ + View for allocating a StockItem to a build output. + """ model = BuildItem form_class = forms.EditBuildItemForm ajax_template_name = 'build/create_build_item.html' - ajax_form_title = _('Allocate new Part') + ajax_form_title = _('Allocate stock to build output') role_required = 'build.add' + # The output StockItem against which the allocation is being made + output = None + + # The "part" which is being allocated to the output part = None + available_stock = None def get_context_data(self): - ctx = super(AjaxCreateView, self).get_context_data() + """ + Provide context data to the template which renders the form. + """ + + ctx = super().get_context_data() if self.part: ctx['part'] = self.part + if self.output: + ctx['output'] = self.output + if self.available_stock: ctx['stock'] = self.available_stock else: @@ -488,73 +824,108 @@ class BuildItemCreate(AjaxCreateView): return ctx + def validate(self, build_item, form, **kwargs): + """ + Extra validation steps as required + """ + + data = form.cleaned_data + + stock_item = data.get('stock_item', None) + quantity = data.get('quantity', None) + + if stock_item: + # Stock item must actually be in stock! + if not stock_item.in_stock: + form.add_error('stock_item', _('Item must be currently in stock')) + + # Check that there are enough items available + if quantity is not None: + available = stock_item.unallocated_quantity() + if quantity > available: + form.add_error('stock_item', _('Stock item is over-allocated')) + form.add_error('quantity', _('Available') + ': ' + str(normalize(available))) + else: + form.add_error('stock_item', _('Stock item must be selected')) + def get_form(self): """ Create Form for making / editing new Part object """ form = super(AjaxCreateView, self).get_form() + self.build = None + self.part = None + self.output = None + # If the Build object is specified, hide the input field. # We do not want the users to be able to move a BuildItem to a different build build_id = form['build'].value() if build_id is not None: + """ + If the build has been provided, hide the widget to change the build selection. + Additionally, update the allowable selections for other fields. + """ form.fields['build'].widget = HiddenInput() + form.fields['install_into'].queryset = StockItem.objects.filter(build=build_id, is_building=True) + self.build = Build.objects.get(pk=build_id) + else: + """ + Build has *not* been selected + """ + pass # If the sub_part is supplied, limit to matching stock items - part_id = self.get_param('part') + part_id = form['part_id'].value() if part_id: try: self.part = Part.objects.get(pk=part_id) - - query = form.fields['stock_item'].queryset - - # Only allow StockItem objects which match the current part - query = query.filter(part=part_id) - - if build_id is not None: - try: - build = Build.objects.get(id=build_id) - - if build.take_from is not None: - # Limit query to stock items that are downstream of the 'take_from' location - query = query.filter(location__in=[loc for loc in build.take_from.getUniqueChildren()]) - - except Build.DoesNotExist: - pass - - # Exclude StockItem objects which are already allocated to this build and part - query = query.exclude(id__in=[item.stock_item.id for item in BuildItem.objects.filter(build=build_id, stock_item__part=part_id)]) - - form.fields['stock_item'].queryset = query - - stocks = query.all() - self.available_stock = stocks - - # If there is only one item selected, select it - if len(stocks) == 1: - form.fields['stock_item'].initial = stocks[0].id - # There is no stock available - elif len(stocks) == 0: - # TODO - Add a message to the form describing the problem - pass - - except Part.DoesNotExist: - self.part = None + + except (ValueError, Part.DoesNotExist): pass + # If the output stock item is specified, hide the input field + output_id = form['install_into'].value() + + if output_id is not None: + + try: + self.output = StockItem.objects.get(pk=output_id) + form.fields['install_into'].widget = HiddenInput() + except (ValueError, StockItem.DoesNotExist): + pass + + else: + # If the output is not specified, but we know that the part is non-trackable, hide the install_into field + if self.part and not self.part.trackable: + form.fields['install_into'].widget = HiddenInput() + + if self.build and self.part: + available_items = self.build.availableStockItems(self.part, self.output) + form.fields['stock_item'].queryset = available_items + + self.available_stock = form.fields['stock_item'].queryset.all() + + # If there is only a single stockitem available, select it! + if len(self.available_stock) == 1: + form.fields['stock_item'].initial = self.available_stock[0].pk + return form def get_initial(self): """ Provide initial data for BomItem. Look for the folllowing in the GET data: - build: pk of the Build object + - part: pk of the Part object which we are assigning + - output: pk of the StockItem object into which the allocated stock will be installed """ initials = super(AjaxCreateView, self).get_initial().copy() build_id = self.get_param('build') part_id = self.get_param('part') + output_id = self.get_param('install_into') # Reference to a Part object part = None @@ -565,10 +936,13 @@ class BuildItemCreate(AjaxCreateView): # Reference to a Build object build = None + # Reference to a StockItem object + output = None + if part_id: try: part = Part.objects.get(pk=part_id) - initials['part'] = part + initials['part_id'] = part.pk except Part.DoesNotExist: pass @@ -579,15 +953,26 @@ class BuildItemCreate(AjaxCreateView): except Build.DoesNotExist: pass + # If the output has been specified + if output_id: + try: + output = StockItem.objects.get(pk=output_id) + initials['install_into'] = output + except (ValueError, StockItem.DoesNotExist): + pass + + # Work out how much stock is required + if build and part: + required_quantity = build.unallocatedQuantity(part, output) + else: + required_quantity = None + quantity = self.request.GET.get('quantity', None) if quantity is not None: quantity = float(quantity) - - if quantity is None: - # Work out how many parts remain to be alloacted for the build - if part: - quantity = build.getUnallocatedQuantity(part) + elif required_quantity is not None: + quantity = required_quantity item_id = self.get_param('item') @@ -595,7 +980,7 @@ class BuildItemCreate(AjaxCreateView): if item_id: try: item = StockItem.objects.get(pk=item_id) - except: + except (ValueError, StockItem.DoesNotExist): pass # If a StockItem is not selected, try to auto-select one @@ -621,7 +1006,7 @@ class BuildItemEdit(AjaxUpdateView): """ View to edit a BuildItem object """ model = BuildItem - ajax_template_name = 'modal_form.html' + ajax_template_name = 'build/edit_build_item.html' form_class = forms.EditBuildItemForm ajax_form_title = _('Edit Stock Allocation') role_required = 'build.change' @@ -632,23 +1017,107 @@ class BuildItemEdit(AjaxUpdateView): } def get_form(self): - """ Create form for editing a BuildItem. + """ + Create form for editing a BuildItem. - Limit the StockItem options to items that match the part """ - build_item = self.get_object() - form = super(BuildItemEdit, self).get_form() - query = StockItem.objects.all() - - if build_item.stock_item: - part_id = build_item.stock_item.part.id - query = query.filter(part=part_id) + # Hide fields which we do not wish the user to edit + for field in ['build', 'stock_item']: + if form[field].value(): + form.fields[field].widget = HiddenInput() - form.fields['stock_item'].queryset = query + form.fields['install_into'].widget = HiddenInput() + return form + + +class BuildAttachmentCreate(AjaxCreateView): + """ + View for creating a BuildAttachment + """ + + model = BuildOrderAttachment + form_class = forms.EditBuildAttachmentForm + ajax_form_title = _('Add Build Order Attachment') + role_required = 'build.add' + + def save(self, form, **kwargs): + """ + Add information on the user that uploaded the attachment + """ + + attachment = form.save(commit=False) + attachment.user = self.request.user + attachment.save() + + def get_data(self): + return { + 'success': _('Added attachment') + } + + def get_initial(self): + """ + Get initial data for creating an attachment + """ + + initials = super().get_initial() + + try: + initials['build'] = Build.objects.get(pk=self.request.GET.get('build', -1)) + except (ValueError, Build.DoesNotExist): + pass + + return initials + + def get_form(self): + """ + Hide the 'build' field if specified + """ + + form = super().get_form() + + form.fields['build'].widget = HiddenInput() + + return form + + +class BuildAttachmentEdit(AjaxUpdateView): + """ + View for editing a BuildAttachment object + """ + + model = BuildOrderAttachment + form_class = forms.EditBuildAttachmentForm + ajax_form_title = _('Edit Attachment') + role_required = 'build.change' + + def get_form(self): + form = super().get_form() form.fields['build'].widget = HiddenInput() return form + + def get_data(self): + return { + 'success': _('Attachment updated') + } + + +class BuildAttachmentDelete(AjaxDeleteView): + """ + View for deleting a BuildAttachment + """ + + model = BuildOrderAttachment + ajax_form_title = _('Delete Attachment') + context_object_name = 'attachment' + role_required = 'build.delete' + + def get_data(self): + return { + 'danger': _('Deleted attachment') + } diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml index 94ec36bec7..ecd0a241cf 100644 --- a/InvenTree/config_template.yaml +++ b/InvenTree/config_template.yaml @@ -29,6 +29,12 @@ timezone: UTC # Set debug to False to run in production mode debug: True +# Set debug_toolbar to True to enable a debugging toolbar for InvenTree +# Note: This will only be displayed if DEBUG mode is enabled, +# and only if InvenTree is accessed from a local IP (127.0.0.1) +debug_toolbar: False + + # Allowed hosts (see ALLOWED_HOSTS in Django settings documentation) # A list of strings representing the host/domain names that this Django site can serve. # Default behaviour is to allow all hosts (THIS IS NOT SECURE!) @@ -63,11 +69,6 @@ static_root: '../inventree_static' # - git # - ssh -# Set debug_toolbar to True to enable a debugging toolbar for InvenTree -# Note: This will only be displayed if DEBUG mode is enabled, -# and only if InvenTree is accessed from a local IP (127.0.0.1) -debug_toolbar: False - # Backup options # Set the backup_dir parameter to store backup files in a specific location # If unspecified, the local user's temp directory will be used diff --git a/InvenTree/locale/de/LC_MESSAGES/django.mo b/InvenTree/locale/de/LC_MESSAGES/django.mo index e496fdd86a..ccc97446ba 100644 Binary files a/InvenTree/locale/de/LC_MESSAGES/django.mo and b/InvenTree/locale/de/LC_MESSAGES/django.mo differ diff --git a/InvenTree/locale/de/LC_MESSAGES/django.po b/InvenTree/locale/de/LC_MESSAGES/django.po index 3ba6dde6d3..8a486733dc 100644 --- a/InvenTree/locale/de/LC_MESSAGES/django.po +++ b/InvenTree/locale/de/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-10-30 04:52+0000\n" +"POT-Creation-Date: 2020-11-03 10:02+0000\n" "PO-Revision-Date: 2020-05-03 11:32+0200\n" "Last-Translator: Christian Schlüter \n" "Language-Team: C \n" @@ -25,7 +25,7 @@ msgstr "Keine Aktion angegeben" msgid "No matching action found" msgstr "Keine passende Aktion gefunden" -#: InvenTree/forms.py:102 build/forms.py:49 +#: InvenTree/forms.py:102 build/forms.py:82 build/forms.py:170 msgid "Confirm" msgstr "Bestätigen" @@ -49,7 +49,7 @@ msgstr "" msgid "Apply Theme" msgstr "" -#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:261 +#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:269 msgid "Invalid quantity provided" msgstr "Keine gültige Menge" @@ -92,12 +92,12 @@ msgstr "Datei zum Anhängen auswählen" msgid "File comment" msgstr "Datei-Kommentar" -#: InvenTree/models.py:68 templates/js/stock.js:700 +#: InvenTree/models.py:68 templates/js/stock.js:734 msgid "User" msgstr "Benutzer" #: InvenTree/models.py:106 part/templates/part/params.html:24 -#: templates/js/part.js:90 +#: templates/js/part.js:129 msgid "Name" msgstr "Name" @@ -124,7 +124,7 @@ msgid "Polish" msgstr "Polnisch" #: InvenTree/status_codes.py:94 InvenTree/status_codes.py:135 -#: InvenTree/status_codes.py:222 templates/js/table_filters.js:181 +#: InvenTree/status_codes.py:222 msgid "Pending" msgstr "Ausstehend" @@ -176,11 +176,11 @@ msgstr "Zerstört" msgid "Rejected" msgstr "" -#: InvenTree/status_codes.py:223 build/templates/build/allocate.html:358 -#: order/templates/order/sales_order_detail.html:225 -#: part/templates/part/tabs.html:23 templates/js/build.js:140 -msgid "Allocated" -msgstr "Zugeordnet" +#: InvenTree/status_codes.py:223 +#, fuzzy +#| msgid "Location" +msgid "Production" +msgstr "Standort" #: InvenTree/validators.py:39 msgid "Invalid character in part name" @@ -214,7 +214,35 @@ msgstr "Überschuss darf 100% nicht überschreiten" msgid "Overage must be an integer value or a percentage" msgstr "Überschuss muss eine Ganzzahl oder ein Prozentwert sein" -#: InvenTree/views.py:752 +#: InvenTree/views.py:493 +#, fuzzy +#| msgid "Delete BOM Item" +msgid "Delete Item" +msgstr "BOM-Position löschen" + +#: InvenTree/views.py:542 +#, 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 +#, fuzzy +#| msgid "No user information" +msgid "Edit User Information" +msgstr "Keine Benutzerinformation" + +#: InvenTree/views.py:568 templates/InvenTree/settings/user.html:22 +#, fuzzy +#| msgid "Select part" +msgid "Set Password" +msgstr "Teil auswählen" + +#: InvenTree/views.py:587 +msgid "Password fields must match" +msgstr "" + +#: InvenTree/views.py:757 msgid "Database Statistics" msgstr "Datenbankstatistiken" @@ -262,121 +290,189 @@ msgstr "" msgid "Barcode associated with StockItem" msgstr "Neues Lagerobjekt hinzufügen" -#: build/forms.py:28 +#: build/forms.py:32 #, fuzzy #| msgid "Order reference" msgid "Build Order reference" msgstr "Bestell-Referenz" -#: build/forms.py:70 -#, fuzzy -#| msgid "Location Details" -msgid "Location of completed parts" -msgstr "Standort-Details" +#: 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 +#: order/templates/order/order_wizard/select_parts.html:32 +#: order/templates/order/purchase_order_detail.html:178 +#: order/templates/order/sales_order_detail.html:74 +#: 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 +#: stock/templates/stock/item_base.html:40 +#: stock/templates/stock/item_base.html:46 +#: stock/templates/stock/item_base.html:197 +#: stock/templates/stock/stock_adjust.html:18 templates/js/barcode.js:338 +#: templates/js/bom.js:189 templates/js/build.js:404 templates/js/stock.js:725 +#: templates/js/stock.js:953 +msgid "Quantity" +msgstr "Anzahl" -#: build/forms.py:74 +#: build/forms.py:71 +#, fuzzy +#| msgid "Serial number for this item" +msgid "Enter quantity for build output" +msgstr "Seriennummer für dieses Teil" + +#: build/forms.py:75 stock/forms.py:111 #, fuzzy #| msgid "Serial Number" msgid "Serial numbers" msgstr "Seriennummer" -#: build/forms.py:76 stock/forms.py:111 -msgid "Enter unique serial numbers (or leave blank)" -msgstr "Eindeutige Seriennummern eingeben (oder leer lassen)" +#: build/forms.py:77 +#, fuzzy +#| msgid "Serial number for this item" +msgid "Enter serial numbers for build outputs" +msgstr "Seriennummer für dieses Teil" -#: build/forms.py:79 +#: build/forms.py:83 +#, fuzzy +#| msgid "Confirm completion of build" +msgid "Confirm creation of build outut" +msgstr "Baufertigstellung bestätigen" + +#: build/forms.py:103 +#, fuzzy +#| msgid "Confirm completion of build" +msgid "Confirm deletion of build output" +msgstr "Baufertigstellung bestätigen" + +#: build/forms.py:124 +#, fuzzy +#| msgid "Confirm unallocation of build stock" +msgid "Confirm unallocation of stock" +msgstr "Zuweisungsaufhebung bestätigen" + +#: build/forms.py:148 +msgid "Confirm stock allocation" +msgstr "Lagerbestandszuordnung bestätigen" + +#: build/forms.py:171 +#, fuzzy +#| msgid "Mark order as complete" +msgid "Mark build as complete" +msgstr "Bestellung als vollständig markieren" + +#: build/forms.py:195 +#, fuzzy +#| msgid "Location Details" +msgid "Location of completed parts" +msgstr "Standort-Details" + +#: build/forms.py:200 +#, fuzzy +#| msgid "Confirm stock allocation" +msgid "Confirm completion with incomplete stock allocation" +msgstr "Lagerbestandszuordnung bestätigen" + +#: build/forms.py:203 msgid "Confirm build completion" msgstr "Bau-Fertigstellung bestätigen" -#: build/models.py:54 build/templates/build/build_base.html:8 +#: build/forms.py:223 build/views.py:68 +msgid "Confirm build cancellation" +msgstr "Bauabbruch bestätigen" + +#: build/forms.py:237 +#, fuzzy +#| msgid "Select stock item to allocate" +msgid "Select quantity of stock to allocate" +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:214 +#: stock/templates/stock/item_base.html:227 msgid "Build Order" msgstr "Bauauftrag" -#: build/models.py:55 build/templates/build/index.html:6 +#: 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 msgid "Build Orders" msgstr "Bauaufträge" -#: build/models.py:77 -#, fuzzy -#| msgid "Overage must be an integer value or a percentage" -msgid "Build quantity must be integer value for trackable parts" -msgstr "Überschuss muss eine Ganzzahl oder ein Prozentwert sein" - -#: build/models.py:86 build/templates/build/build_base.html:73 +#: build/models.py:72 #, fuzzy #| msgid "Order Reference" msgid "Build Order Reference" msgstr "Bestellreferenz" -#: build/models.py:87 build/templates/build/allocate.html:342 -#: order/templates/order/purchase_order_detail.html:173 templates/js/bom.js:195 +#: build/models.py:73 order/templates/order/purchase_order_detail.html:173 +#: templates/js/bom.js:181 templates/js/build.js:493 msgid "Reference" msgstr "Referenz" -#: build/models.py:94 build/templates/build/allocate.html:337 +#: build/models.py:80 build/templates/build/detail.html:19 #: company/templates/company/supplier_part_base.html:61 #: company/templates/company/supplier_part_detail.html:27 #: order/templates/order/purchase_order_detail.html:160 #: part/templates/part/detail.html:51 part/templates/part/set_category.html:14 -#: templates/InvenTree/search.html:147 templates/js/bom.js:188 -#: templates/js/bom.js:515 templates/js/build.js:56 templates/js/company.js:56 -#: templates/js/order.js:167 templates/js/order.js:249 templates/js/part.js:149 -#: templates/js/part.js:232 templates/js/part.js:384 templates/js/part.js:565 -#: templates/js/stock.js:445 templates/js/stock.js:672 +#: 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/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" msgstr "Beschreibung" -#: build/models.py:97 +#: build/models.py:83 msgid "Brief description of the build" msgstr "Kurze Beschreibung des Baus" -#: build/models.py:105 build/templates/build/build_base.html:94 +#: build/models.py:91 build/templates/build/build_base.html:94 +#: build/templates/build/detail.html:75 msgid "Parent Build" msgstr "Eltern-Bau" -#: build/models.py:106 -msgid "Parent build to which this build is allocated" -msgstr "Eltern-Bau, dem dieser Bau zugewiesen ist" +#: build/models.py:92 +#, fuzzy +#| msgid "SalesOrder to which this build is allocated" +msgid "BuildOrder to which this build is allocated" +msgstr "Bestellung, die diesem Bau zugwiesen ist" -#: build/models.py:111 build/templates/build/allocate.html:329 -#: build/templates/build/auto_allocate.html:19 -#: build/templates/build/build_base.html:78 -#: build/templates/build/detail.html:22 order/models.py:501 +#: 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 #: 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:293 #: part/templates/part/part_app_base.html:7 #: 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:500 -#: templates/js/build.js:61 templates/js/company.js:138 -#: templates/js/part.js:213 templates/js/part.js:318 templates/js/stock.js:421 -#: templates/js/stock.js:978 +#: templates/js/barcode.js:336 templates/js/bom.js:147 templates/js/bom.js:484 +#: templates/js/build.js:647 templates/js/company.js:138 +#: templates/js/part.js:252 templates/js/part.js:357 templates/js/stock.js:468 +#: templates/js/stock.js:1025 msgid "Part" msgstr "Teil" -#: build/models.py:120 +#: build/models.py:105 msgid "Select part to build" msgstr "Teil für den Bau wählen" -#: build/models.py:125 +#: build/models.py:110 msgid "Sales Order Reference" msgstr "Bestellungsreferenz" -#: build/models.py:129 +#: build/models.py:114 msgid "SalesOrder to which this build is allocated" msgstr "Bestellung, die diesem Bau zugwiesen ist" -#: build/models.py:134 +#: build/models.py:119 msgid "Source Location" msgstr "Quell-Standort" -#: build/models.py:138 +#: build/models.py:123 msgid "" "Select location to take stock from for this build (leave blank to take from " "any stock location)" @@ -384,14 +480,38 @@ msgstr "" "Lager-Entnahmestandort für diesen Bau wählen (oder leer lassen für einen " "beliebigen Lager-Standort)" -#: build/models.py:142 +#: build/models.py:128 +#, fuzzy +#| msgid "Destination stock location" +msgid "Destination Location" +msgstr "Ziel-Lagerbestand" + +#: build/models.py:132 +msgid "Select location where the completed items will be stored" +msgstr "" + +#: build/models.py:136 msgid "Build Quantity" msgstr "Bau-Anzahl" -#: build/models.py:145 -msgid "Number of parts to build" +#: build/models.py:139 +#, fuzzy +#| msgid "Number of parts to build" +msgid "Number of stock items to build" msgstr "Anzahl der zu bauenden Teile" +#: build/models.py:143 +#, fuzzy +#| msgid "Completed" +msgid "Completed items" +msgstr "Fertig" + +#: build/models.py:145 +#, fuzzy +#| msgid "Delete this Stock Item when stock is depleted" +msgid "Number of stock items which have been completed" +msgstr "Objekt löschen wenn Lagerbestand aufgebraucht" + #: build/models.py:149 part/templates/part/part_base.html:155 msgid "Build Status" msgstr "Bau-Status" @@ -400,7 +520,7 @@ msgstr "Bau-Status" msgid "Build status code" msgstr "Bau-Statuscode" -#: build/models.py:157 stock/models.py:387 +#: build/models.py:157 stock/models.py:389 msgid "Batch Code" msgstr "Losnummer" @@ -408,15 +528,15 @@ msgstr "Losnummer" msgid "Batch code for this build output" msgstr "Chargennummer für diese Bau-Ausgabe" -#: build/models.py:176 build/templates/build/detail.html:55 +#: build/models.py:176 build/templates/build/detail.html:89 #: company/templates/company/supplier_part_base.html:68 #: company/templates/company/supplier_part_detail.html:24 #: part/templates/part/detail.html:80 part/templates/part/part_base.html:102 -#: stock/models.py:381 stock/templates/stock/item_base.html:266 +#: stock/models.py:383 stock/templates/stock/item_base.html:279 msgid "External Link" msgstr "Externer Link" -#: build/models.py:177 part/models.py:565 stock/models.py:383 +#: build/models.py:177 part/models.py:596 stock/models.py:385 msgid "Link to external URL" msgstr "Link zu einer externen URL" @@ -424,10 +544,10 @@ 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:70 -#: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:453 -#: stock/models.py:1404 stock/templates/stock/tabs.html:26 -#: templates/js/barcode.js:391 templates/js/bom.js:264 -#: templates/js/stock.js:116 templates/js/stock.js:544 +#: 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 +#: templates/js/barcode.js:391 templates/js/bom.js:250 +#: templates/js/stock.js:116 templates/js/stock.js:578 msgid "Notes" msgstr "Notizen" @@ -435,163 +555,175 @@ msgstr "Notizen" msgid "Extra build notes" msgstr "Notizen für den Bau" -#: build/models.py:520 +#: build/models.py:543 +#, fuzzy +#| msgid "No action specified" +msgid "No build output specified" +msgstr "Keine Aktion angegeben" + +#: build/models.py:546 +msgid "Build output is already completed" +msgstr "" + +#: build/models.py:549 +#, fuzzy +#| msgid "Quantity does not match serial numbers" +msgid "Build output does not match Build Order" +msgstr "Anzahl stimmt nicht mit den Seriennummern überein" + +#: build/models.py:620 +#, fuzzy +#| msgid "Complete Build" +msgid "Completed build output" +msgstr "Bau fertigstellen" + +#: build/models.py:858 +msgid "BuildItem must be unique for build, stock_item and install_into" +msgstr "" + +#: build/models.py:880 +#, fuzzy +#| msgid "Allocate Stock to Build" +msgid "Build item must specify a build output" +msgstr "Lagerbestand dem Bau zuweisen" + +#: build/models.py:885 #, python-brace-format msgid "Selected stock item not found in BOM for part '{p}'" msgstr "Ausgewähltes Lagerobjekt nicht in BOM für Teil '{p}' gefunden" -#: build/models.py:523 +#: build/models.py:889 #, python-brace-format msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" msgstr "" "zugewiesene Anzahl ({n}) darf nicht die verfügbare ({q}) Anzahl überschreiten" -#: build/models.py:529 order/models.py:585 +#: build/models.py:896 order/models.py:603 msgid "StockItem is over-allocated" msgstr "Zu viele Lagerobjekte zugewiesen" -#: build/models.py:532 order/models.py:588 +#: build/models.py:900 order/models.py:606 msgid "Allocation quantity must be greater than zero" msgstr "Anzahl muss größer null sein" -#: build/models.py:535 +#: build/models.py:904 msgid "Quantity must be 1 for serialized stock" msgstr "Anzahl muss 1 für Objekte mit Seriennummer sein" -#: build/models.py:564 +#: build/models.py:944 msgid "Build to allocate parts" msgstr "Bau starten um Teile zuzuweisen" -#: build/models.py:571 -msgid "Stock Item to allocate to build" -msgstr "Lagerobjekt dem Bau zuweisen" +#: build/models.py:951 +#, fuzzy +#| msgid "Remove stock" +msgid "Source stock item" +msgstr "Bestand entfernen" -#: build/models.py:584 +#: build/models.py:964 msgid "Stock quantity to allocate to build" msgstr "Lagerobjekt-Anzahl dem Bau zuweisen" -#: build/templates/build/allocate.html:17 -#: company/templates/company/detail_part.html:28 order/views.py:804 +#: build/models.py:972 +#, fuzzy +#| msgid "Destination stock location" +msgid "Destination stock item" +msgstr "Ziel-Lagerbestand" + +#: build/templates/build/allocate.html:14 +#, fuzzy +#| msgid "Complete Build" +msgid "Incomplete Build Ouputs" +msgstr "Bau fertigstellen" + +#: build/templates/build/allocate.html:20 +#, fuzzy +#| msgid "Build order allocation is complete" +msgid "Build order has been completed" +msgstr "Bau-Zuweisung ist vollständig" + +#: build/templates/build/allocate.html:24 +#, fuzzy +#| msgid "Created new build" +msgid "Create new build output" +msgstr "Neuen Bau angelegt" + +#: build/templates/build/allocate.html:25 +#, fuzzy +#| msgid "Create New Part" +msgid "Create New Output" +msgstr "Neues Teil anlegen" + +#: build/templates/build/allocate.html:28 +#, fuzzy +#| msgid "Order part" +msgid "Order required parts" +msgstr "Teil bestellen" + +#: build/templates/build/allocate.html:29 +#: company/templates/company/detail_part.html:28 order/views.py:801 #: part/templates/part/category.html:125 msgid "Order Parts" msgstr "Teile bestellen" -#: build/templates/build/allocate.html:18 -msgid "Automatically allocate stock" -msgstr "Lagerbestand automatisch zuweisen" - -#: build/templates/build/allocate.html:18 -msgid "Auto Allocate" -msgstr "Automatisches Zuweisen" - -#: build/templates/build/allocate.html:19 -msgid "Unallocate" +#: build/templates/build/allocate.html:32 templates/js/build.js:574 +#, fuzzy +#| msgid "Unallocate Stock" +msgid "Unallocate stock" msgstr "Zuweisung aufheben" -#: build/templates/build/allocate.html:87 templates/stock_table.html:13 -msgid "New Stock Item" -msgstr "Neues Lagerobjekt" +#: build/templates/build/allocate.html:33 build/views.py:341 build/views.py:778 +msgid "Unallocate Stock" +msgstr "Zuweisung aufheben" -#: build/templates/build/allocate.html:88 stock/views.py:1459 -msgid "Create new Stock Item" -msgstr "Neues Lagerobjekt hinzufügen" +#: build/templates/build/allocate.html:46 +#, fuzzy +#| msgid "Created new build" +msgid "Create a new build output" +msgstr "Neuen Bau angelegt" -#: build/templates/build/allocate.html:170 -#: order/templates/order/sales_order_detail.html:72 -#: order/templates/order/sales_order_detail.html:154 stock/models.py:375 -#: stock/templates/stock/item_base.html:178 -msgid "Serial Number" -msgstr "Seriennummer" +#: build/templates/build/allocate.html:47 +#, fuzzy +#| msgid "Complete Build" +msgid "No incomplete build outputs remain." +msgstr "Bau fertigstellen" -#: build/templates/build/allocate.html:172 -#: build/templates/build/auto_allocate.html:20 -#: build/templates/build/build_base.html:83 -#: build/templates/build/detail.html:27 -#: 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/sales_order_detail.html:74 -#: 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 -#: stock/templates/stock/item_base.html:26 -#: stock/templates/stock/item_base.html:32 -#: stock/templates/stock/item_base.html:184 -#: stock/templates/stock/stock_adjust.html:18 templates/js/barcode.js:338 -#: templates/js/bom.js:203 templates/js/build.js:72 templates/js/stock.js:691 -#: templates/js/stock.js:906 -msgid "Quantity" -msgstr "Anzahl" +#: build/templates/build/allocate.html:48 +msgid "Create a new build output using the button above" +msgstr "" -#: build/templates/build/allocate.html:186 -#: build/templates/build/auto_allocate.html:21 stock/forms.py:336 -#: stock/templates/stock/item_base.html:220 -#: stock/templates/stock/stock_adjust.html:17 -#: templates/InvenTree/search.html:183 templates/js/barcode.js:337 -#: templates/js/stock.js:519 -msgid "Location" -msgstr "Standort" - -#: build/templates/build/allocate.html:210 -#: order/templates/order/sales_order_detail.html:96 templates/js/build.js:144 -msgid "Edit stock allocation" -msgstr "Lagerobjekt-Standort bearbeiten" - -#: build/templates/build/allocate.html:211 -#: order/templates/order/sales_order_detail.html:97 templates/js/build.js:145 -msgid "Delete stock allocation" -msgstr "Zuweisung löschen" - -#: build/templates/build/allocate.html:238 templates/js/bom.js:362 -msgid "No BOM items found" -msgstr "Keine BOM-Einträge gefunden" - -#: build/templates/build/allocate.html:347 part/models.py:1425 -#: templates/js/part.js:569 templates/js/table_filters.js:167 -msgid "Required" -msgstr "benötigt" - -#: build/templates/build/allocate.html:356 -msgid "Assigned" -msgstr "Zugewiesen" - -#: build/templates/build/allocate.html:394 -#: order/templates/order/sales_order_detail.html:275 -msgid "Buy parts" -msgstr "Teile kaufen" - -#: build/templates/build/allocate.html:398 -#: order/templates/order/sales_order_detail.html:279 -msgid "Build parts" -msgstr "Bauteile" - -#: build/templates/build/allocate.html:401 -msgid "Allocate stock" -msgstr "Lagerbestand zuweisen" +#: build/templates/build/attachments.html:11 build/templates/build/tabs.html:17 +#: order/templates/order/po_tabs.html:11 order/templates/order/so_tabs.html:16 +#: part/templates/part/tabs.html:67 stock/templates/stock/tabs.html:32 +msgid "Attachments" +msgstr "Anhänge" #: build/templates/build/auto_allocate.html:9 msgid "Automatically Allocate Stock" msgstr "Lagerbestand automatisch zuweisen" #: build/templates/build/auto_allocate.html:10 +#, fuzzy +#| msgid "Stock Item to allocate to build" msgid "" -"Stock Items are selected for automatic allocation if there is only a single " -"stock item available." -msgstr "" -"Teile werden automatisch zugewiesen, wenn nur ein Lagerobjekt verfügbar ist" +"The following stock items will be allocated to the specified build output" +msgstr "Lagerobjekt dem Bau zuweisen" -#: build/templates/build/auto_allocate.html:11 -msgid "The following stock items will be allocated to the build:" -msgstr "Folgende Lagerobjekte werden dem Bau automatisch zugewiesen:" +#: build/templates/build/auto_allocate.html:18 stock/forms.py:336 +#: stock/templates/stock/item_base.html:233 +#: 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 +msgid "Location" +msgstr "Standort" -#: build/templates/build/auto_allocate.html:40 +#: build/templates/build/auto_allocate.html:37 #, fuzzy #| msgid "No stock items found that can be allocated to this build" msgid "No stock items found that can be automatically allocated to this build" msgstr "Keine Lagerobjekt gefunden, die diesem Bau zugewiesen werden können" -#: build/templates/build/auto_allocate.html:42 +#: build/templates/build/auto_allocate.html:39 #, fuzzy #| msgid "StockItem has been allocated" msgid "Stock items will have to be manually allocated" @@ -614,7 +746,7 @@ msgstr "Dieser Bau ist Kind von Bau" #: order/templates/order/order_base.html:26 #: order/templates/order/sales_order_base.html:35 #: part/templates/part/category.html:13 part/templates/part/part_base.html:32 -#: stock/templates/stock/item_base.html:76 +#: stock/templates/stock/item_base.html:90 #: stock/templates/stock/location.html:12 #, fuzzy #| msgid "Admin" @@ -627,7 +759,7 @@ msgstr "Admin" msgid "Edit Build" msgstr "Bau bearbeitet" -#: build/templates/build/build_base.html:50 build/views.py:190 +#: build/templates/build/build_base.html:50 msgid "Complete Build" msgstr "Bau fertigstellen" @@ -635,7 +767,7 @@ msgstr "Bau fertigstellen" msgid "Cancel Build" msgstr "Bau abbrechen" -#: build/templates/build/build_base.html:59 build/views.py:456 +#: build/templates/build/build_base.html:59 build/views.py:767 msgid "Delete Build" msgstr "Bau entfernt" @@ -643,126 +775,198 @@ msgstr "Bau entfernt" msgid "Build Details" msgstr "Bau-Status" -#: build/templates/build/build_base.html:88 -#: build/templates/build/detail.html:42 +#: 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:298 templates/InvenTree/search.html:175 -#: templates/js/barcode.js:42 templates/js/build.js:77 +#: stock/templates/stock/item_base.html:311 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:506 templates/js/stock.js:914 +#: templates/js/stock.js:557 templates/js/stock.js:961 msgid "Status" msgstr "Status" -#: build/templates/build/build_base.html:101 order/models.py:499 +#: build/templates/build/build_base.html:88 +#: build/templates/build/detail.html:62 +msgid "Progress" +msgstr "" + +#: build/templates/build/build_base.html:101 +#: build/templates/build/detail.html:82 order/models.py:517 #: 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:208 templates/js/order.js:221 +#: stock/templates/stock/item_base.html:221 templates/js/order.js:221 msgid "Sales Order" msgstr "Bestellung" -#: build/templates/build/build_base.html:107 -msgid "BOM Price" -msgstr "Stücklistenpreis" - -#: build/templates/build/build_base.html:112 -msgid "BOM pricing is incomplete" -msgstr "Stücklistenbepreisung ist unvollständig" - -#: build/templates/build/build_base.html:115 -msgid "No pricing information" -msgstr "Keine Preisinformation" - #: build/templates/build/build_output.html:9 build/templates/build/tabs.html:11 msgid "Build Outputs" msgstr "Bau-Ausgabe" -#: build/templates/build/complete.html:6 -#: stock/templates/stock/item_base.html:245 templates/js/build.js:40 -#: templates/navbar.html:25 -msgid "Build" -msgstr "Bau" +#: build/templates/build/build_output_create.html:7 +msgid "The Bill of Materials contains trackable parts" +msgstr "" -#: build/templates/build/complete.html:10 -msgid "Build order allocation is complete" +#: build/templates/build/build_output_create.html:8 +msgid "Build outputs must be generated individually." +msgstr "" + +#: build/templates/build/build_output_create.html:9 +msgid "Multiple build outputs will be created based on the quantity specified." +msgstr "" + +#: build/templates/build/build_output_create.html:15 +msgid "Trackable parts can have serial numbers specified" +msgstr "" + +#: build/templates/build/build_output_create.html:16 +#, fuzzy +#| msgid "Serial number for this item" +msgid "Enter serial numbers to generate multiple single build outputs" +msgstr "Seriennummer für dieses Teil" + +#: build/templates/build/cancel.html:5 +#, fuzzy +#| msgid "Are you sure you wish to unallocate all stock for this build?" +msgid "Are you sure you wish to cancel this build?" +msgstr "" +"Sind Sie sicher, dass sie alle Lagerobjekte von diesem Bau entfernen möchten?" + +#: build/templates/build/complete.html:8 +#, fuzzy +#| msgid "Build order allocation is complete" +msgid "Build can be completed" msgstr "Bau-Zuweisung ist vollständig" -#: build/templates/build/complete.html:14 -msgid "Warning: Build order allocation is not complete" -msgstr "Warnung: Bau-Zuweisung ist unvollständig" +#: build/templates/build/complete.html:12 +#, fuzzy +#| msgid "Build order allocation is complete" +msgid "Build cannot be completed" +msgstr "Bau-Zuweisung ist vollständig" #: build/templates/build/complete.html:15 -msgid "" -"Build Order has not been fully allocated. Ensure that all Stock Items have " -"been allocated to the Build" -msgstr "" -"Bau-Zuweisung ist unvollständig. Sicherstellen, dass alle Lagerobjekte dem " -"Bau zugewiesen wurden" +#, fuzzy +#| msgid "Complete Build" +msgid "Incompleted build outputs remain" +msgstr "Bau fertigstellen" -#: build/templates/build/complete.html:20 -msgid "The following actions will be performed:" -msgstr "Die folgenden Aktionen werden ausgeführt:" +#: build/templates/build/complete.html:18 +#, fuzzy +#| msgid "Build order allocation is complete" +msgid "Required build quantity has not been completed" +msgstr "Bau-Zuweisung ist vollständig" -#: build/templates/build/complete.html:22 -msgid "Remove allocated items from stock" -msgstr "Zugewiesene Teile dem Lager entnehmen" +#: build/templates/build/complete_output.html:9 +#, fuzzy +#| msgid "Build order allocation is complete" +msgid "Stock allocation is complete" +msgstr "Bau-Zuweisung ist vollständig" -#: build/templates/build/complete.html:23 -msgid "Add completed items to stock" -msgstr "Komplettierte Teile dem Lager hinzufügen" +#: build/templates/build/complete_output.html:13 +#, fuzzy +#| msgid "Build order allocation is complete" +msgid "Stock allocation is incomplete" +msgstr "Bau-Zuweisung ist vollständig" -#: build/templates/build/complete.html:29 +#: build/templates/build/complete_output.html:19 +#, fuzzy +#| msgid "This SalesOrder has not been fully allocated" +msgid "parts have not been fully allocated" +msgstr "Dieser Auftrag ist nicht vollständig zugeordnet" + +#: build/templates/build/complete_output.html:40 msgid "The following items will be created" msgstr "Die folgenden Objekte werden erstellt" -#: build/templates/build/delete_build_item.html:6 -msgid "Are you sure you want to unallocate these parts?" +#: build/templates/build/create_build_item.html:7 +#, fuzzy +#| msgid "Stock Item to allocate to build" +msgid "Select a stock item to allocate to the selected build output" +msgstr "Lagerobjekt dem Bau zuweisen" + +#: build/templates/build/create_build_item.html:11 +#, fuzzy +#| msgid "The following stock items will be allocated to the build:" +msgid "The allocated stock will be installed into the following build output:" +msgstr "Folgende Lagerobjekte werden dem Bau automatisch zugewiesen:" + +#: build/templates/build/create_build_item.html:19 +#, fuzzy +#| msgid "Stock available" +msgid "No stock available for" +msgstr "Bestand verfügbar" + +#: build/templates/build/delete_build_item.html:8 +#, fuzzy +#| msgid "Are you sure you want to unallocate these parts?" +msgid "Are you sure you want to unallocate this stock?" msgstr "Sind Sie sicher, dass sie die folgenden Teile entfernen möchten?" -#: build/templates/build/detail.html:17 -msgid "Title" -msgstr "Titel" +#: build/templates/build/delete_build_item.html:11 +#, fuzzy +#| msgid "The following stock items will be allocated to the build:" +msgid "The selected stock will be unallocated from the build output" +msgstr "Folgende Lagerobjekte werden dem Bau automatisch zugewiesen:" -#: build/templates/build/detail.html:31 +#: build/templates/build/detail.html:33 msgid "Stock Source" msgstr "Lagerobjekt" -#: build/templates/build/detail.html:36 +#: build/templates/build/detail.html:38 msgid "Stock can be taken from any available location." msgstr "Bestand kann jedem verfügbaren Lagerort entnommen werden." -#: build/templates/build/detail.html:48 -#: stock/templates/stock/item_base.html:238 templates/js/stock.js:514 -#: templates/js/stock.js:921 templates/js/table_filters.js:80 -#: templates/js/table_filters.js:146 +#: build/templates/build/detail.html:44 stock/forms.py:364 +#, fuzzy +#| msgid "Description" +msgid "Destination" +msgstr "Beschreibung" + +#: build/templates/build/detail.html:51 +#, fuzzy +#| msgid "Does this part have tracking for unique items?" +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 +#: templates/js/stock.js:968 templates/js/table_filters.js:80 +#: templates/js/table_filters.js:151 msgid "Batch" msgstr "Los" -#: build/templates/build/detail.html:61 +#: build/templates/build/detail.html:95 #: order/templates/order/order_base.html:98 -#: order/templates/order/sales_order_base.html:100 templates/js/build.js:85 +#: order/templates/order/sales_order_base.html:100 templates/js/build.js:683 msgid "Created" msgstr "Erstellt" -#: build/templates/build/detail.html:67 -msgid "Enough Parts?" -msgstr "Genügend Teile?" +#: build/templates/build/detail.html:105 +msgid "BOM Price" +msgstr "Stücklistenpreis" -#: build/templates/build/detail.html:70 -msgid "Yes" -msgstr "Ja" +#: build/templates/build/detail.html:110 +msgid "BOM pricing is incomplete" +msgstr "Stücklistenbepreisung ist unvollständig" -#: build/templates/build/detail.html:72 -msgid "No" -msgstr "Nein" +#: build/templates/build/detail.html:113 +msgid "No pricing information" +msgstr "Keine Preisinformation" -#: build/templates/build/detail.html:80 templates/js/build.js:90 +#: build/templates/build/detail.html:120 templates/js/build.js:661 +#: templates/js/build.js:688 msgid "Completed" msgstr "Fertig" -#: build/templates/build/index.html:25 build/views.py:403 +#: build/templates/build/edit_build_item.html:7 +#, fuzzy +#| msgid "Stock quantity to allocate to build" +msgid "Alter the quantity of stock allocated to the build output" +msgstr "Lagerobjekt-Anzahl dem Bau zuweisen" + +#: build/templates/build/index.html:25 build/views.py:658 msgid "New Build Order" msgstr "Neuer Bauauftrag" @@ -790,95 +994,208 @@ msgid "Details" msgstr "Details" #: build/templates/build/tabs.html:8 -msgid "Allocated Parts" +#, fuzzy +#| msgid "Allocated Parts" +msgid "Allocate Parts" msgstr "Zugeordnete Teile" -#: build/templates/build/unallocate.html:8 +#: build/templates/build/unallocate.html:10 msgid "Are you sure you wish to unallocate all stock for this build?" msgstr "" "Sind Sie sicher, dass sie alle Lagerobjekte von diesem Bau entfernen möchten?" -#: build/views.py:77 -msgid "Confirm build cancellation" -msgstr "Bauabbruch bestätigen" +#: build/templates/build/unallocate.html:12 +#, fuzzy +#| msgid "The following stock items will be allocated to the build:" +msgid "All incomplete stock allocations will be removed from the build" +msgstr "Folgende Lagerobjekte werden dem Bau automatisch zugewiesen:" -#: build/views.py:82 +#: build/views.py:79 msgid "Build was cancelled" msgstr "Bau wurde abgebrochen" -#: build/views.py:98 +#: build/views.py:93 msgid "Allocate Stock" msgstr "Lagerbestand zuweisen" -#: build/views.py:112 -msgid "No matching build found" -msgstr "Kein passender Bau gefunden" +#: build/views.py:157 build/views.py:317 build/views.py:490 +#, fuzzy +#| msgid "No action specified" +msgid "Build output must be specified" +msgstr "Keine Aktion angegeben" -#: build/views.py:131 -msgid "Confirm stock allocation" -msgstr "Lagerbestandszuordnung bestätigen" +#: build/views.py:171 +#, fuzzy +#| msgid "Allocate Stock to Build" +msgid "Allocated stock to build output" +msgstr "Lagerbestand dem Bau zuweisen" -#: build/views.py:132 -msgid "Check the confirmation box at the bottom of the list" -msgstr "Bestätigunsbox am Ende der Liste bestätigen" +#: build/views.py:183 +#, fuzzy +#| msgid "Build Outputs" +msgid "Create Build Output" +msgstr "Bau-Ausgabe" -#: build/views.py:152 build/views.py:467 -msgid "Unallocate Stock" -msgstr "Zuweisung aufheben" +#: build/views.py:207 stock/models.py:832 stock/views.py:1645 +#, fuzzy +#| msgid "Serial numbers already exist: " +msgid "Serial numbers already exist" +msgstr "Seriennummern existieren bereits:" -#: build/views.py:166 +#: build/views.py:216 +#, fuzzy +#| msgid "Serial number for this item" +msgid "Serial numbers required for trackable build output" +msgstr "Seriennummer für dieses Teil" + +#: build/views.py:282 +#, fuzzy +#| msgid "Delete Build" +msgid "Delete Build Output" +msgstr "Bau entfernt" + +#: build/views.py:302 build/views.py:387 msgid "Confirm unallocation of build stock" msgstr "Zuweisungsaufhebung bestätigen" -#: build/views.py:167 stock/views.py:421 +#: build/views.py:303 build/views.py:388 stock/views.py:413 msgid "Check the confirmation box" msgstr "Bestätigungsbox bestätigen" -#: build/views.py:270 -msgid "Confirm completion of build" +#: build/views.py:315 +#, fuzzy +#| msgid "Quantity does not match serial numbers" +msgid "Build output does not match build" +msgstr "Anzahl stimmt nicht mit den Seriennummern überein" + +#: build/views.py:329 +#, fuzzy +#| msgid "No action specified" +msgid "Build output deleted" +msgstr "Keine Aktion angegeben" + +#: build/views.py:412 +#, fuzzy +#| msgid "Complete Build" +msgid "Complete Build Order" +msgstr "Bau fertigstellen" + +#: build/views.py:418 +#, fuzzy +#| msgid "Build order allocation is complete" +msgid "Build order cannot be completed" +msgstr "Bau-Zuweisung ist vollständig" + +#: build/views.py:429 +#, fuzzy +#| msgid "Complete Build" +msgid "Completed build order" +msgstr "Bau fertigstellen" + +#: build/views.py:445 +#, fuzzy +#| msgid "Complete Build" +msgid "Complete Build Output" +msgstr "Bau fertigstellen" + +#: build/views.py:481 +#, fuzzy +#| msgid "Quantity must not exceed available stock quantity ({n})" +msgid "Quantity to complete cannot exceed build output quantity" +msgstr "Anzahl darf nicht die verfügbare Anzahl überschreiten ({n})" + +#: build/views.py:487 +#, fuzzy +#| msgid "Confirm completion of build" +msgid "Confirm completion of incomplete build" msgstr "Baufertigstellung bestätigen" -#: build/views.py:277 -msgid "Invalid location selected" -msgstr "Ungültige Ortsauswahl" +#: build/views.py:578 +#, fuzzy +#| msgid "Build order allocation is complete" +msgid "Build output completed" +msgstr "Bau-Zuweisung ist vollständig" -#: build/views.py:302 stock/views.py:1653 -#, python-brace-format -msgid "The following serial numbers already exist: ({sn})" -msgstr "Die folgende Seriennummer existiert bereits: ({sn})" - -#: build/views.py:323 -msgid "Build marked as COMPLETE" -msgstr "Bau als FERTIG markiert" - -#: build/views.py:431 +#: build/views.py:703 msgid "Created new build" msgstr "Neuen Bau angelegt" -#: build/views.py:441 +#: build/views.py:724 msgid "Edit Build Details" msgstr "Baudetails bearbeiten" -#: build/views.py:447 +#: build/views.py:758 msgid "Edited build" msgstr "Bau bearbeitet" -#: build/views.py:473 +#: build/views.py:784 msgid "Removed parts from build allocation" msgstr "Teile von Bauzuordnung entfernt" -#: build/views.py:483 -msgid "Allocate new Part" -msgstr "Neues Teil zuordnen" +#: build/views.py:796 +#, fuzzy +#| msgid "Allocate Stock to Build" +msgid "Allocate stock to build output" +msgstr "Lagerbestand dem Bau zuweisen" -#: build/views.py:637 +#: build/views.py:840 +#, fuzzy +#| msgid "This stock item is allocated to Build" +msgid "Item must be currently in stock" +msgstr "Dieses Lagerobjekt ist dem Bau zugewiesen" + +#: build/views.py:846 +#, fuzzy +#| msgid "StockItem is over-allocated" +msgid "Stock item is over-allocated" +msgstr "Zu viele Lagerobjekte zugewiesen" + +#: build/views.py:847 templates/js/bom.js:215 templates/js/build.js:503 +#: templates/js/build.js:731 +msgid "Available" +msgstr "verfügbar" + +#: build/views.py:849 +#, fuzzy +#| msgid "StockItem has been allocated" +msgid "Stock item must be selected" +msgstr "Lagerobjekt wurde zugewiesen" + +#: build/views.py:1011 msgid "Edit Stock Allocation" msgstr "Teilzuordnung bearbeiten" -#: build/views.py:642 +#: build/views.py:1016 msgid "Updated Build Item" msgstr "Bauobjekt aktualisiert" +#: build/views.py:1045 +#, fuzzy +#| msgid "Add Sales Order Attachment" +msgid "Add Build Order Attachment" +msgstr "Auftragsanhang hinzufügen" + +#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:96 +#: stock/views.py:176 +msgid "Added attachment" +msgstr "Anhang hinzugefügt" + +#: build/views.py:1095 order/views.py:191 order/views.py:213 +msgid "Edit Attachment" +msgstr "Anhang bearbeiten" + +#: build/views.py:1106 order/views.py:196 order/views.py:218 +msgid "Attachment updated" +msgstr "Anhang aktualisiert" + +#: build/views.py:1116 order/views.py:233 order/views.py:248 +msgid "Delete Attachment" +msgstr "Anhang löschen" + +#: build/views.py:1122 order/views.py:240 order/views.py:255 stock/views.py:234 +msgid "Deleted attachment" +msgstr "Anhang gelöscht" + #: common/models.py:51 #, fuzzy #| msgid "Instance Name" @@ -1122,8 +1439,8 @@ 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:335 -#: stock/templates/stock/item_base.html:164 +#: company/models.py:283 stock/models.py:337 +#: stock/templates/stock/item_base.html:177 msgid "Base Part" msgstr "Basisteil" @@ -1164,7 +1481,7 @@ msgid "Part packaging" msgstr "Teile-Packaging" #: company/templates/company/assigned_stock.html:9 -#: company/templates/company/tabs.html:25 +#: company/templates/company/tabs.html:25 templates/js/build.js:395 #, fuzzy #| msgid "Assigned" msgid "Assigned Stock" @@ -1196,14 +1513,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:273 templates/js/company.js:48 +#: stock/templates/stock/item_base.html:286 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:370 -#: stock/models.py:371 stock/templates/stock/item_base.html:191 +#: order/templates/order/sales_order_base.html:81 stock/models.py:372 +#: stock/models.py:373 stock/templates/stock/item_base.html:204 #: templates/js/company.js:40 templates/js/order.js:236 msgid "Customer" msgstr "Kunde" @@ -1219,7 +1536,7 @@ msgstr "Neues Zuliefererteil anlegen" #: company/templates/company/detail_part.html:18 #: order/templates/order/purchase_order_detail.html:68 -#: part/templates/part/supplier.html:14 templates/js/stock.js:798 +#: part/templates/part/supplier.html:14 templates/js/stock.js:845 msgid "New Supplier Part" msgstr "Neues Zulieferer-Teil" @@ -1247,7 +1564,7 @@ msgid "Delete Parts" msgstr "Teile löschen" #: company/templates/company/detail_part.html:63 -#: part/templates/part/category.html:116 templates/js/stock.js:792 +#: part/templates/part/category.html:116 templates/js/stock.js:839 msgid "New Part" msgstr "Neues Teil" @@ -1279,7 +1596,7 @@ msgstr "Zuliefererbestand" #: company/templates/company/detail_stock.html:35 #: company/templates/company/supplier_part_stock.html:33 -#: part/templates/part/bom.html:67 part/templates/part/category.html:112 +#: part/templates/part/bom.html:63 part/templates/part/category.html:112 #: part/templates/part/category.html:126 part/templates/part/stock.html:51 #: templates/stock_table.html:7 msgid "Export" @@ -1340,8 +1657,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:344 -#: stock/templates/stock/item_base.html:278 templates/js/company.js:180 +#: company/templates/company/supplier_part_base.html:19 stock/models.py:346 +#: stock/templates/stock/item_base.html:291 templates/js/company.js:180 msgid "Supplier Part" msgstr "Zulieferer-Teil" @@ -1398,7 +1715,7 @@ 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:2281 +#: part/templates/part/sale_prices.html:13 part/views.py:2292 msgid "Add Price Break" msgstr "Preisstaffel hinzufügen" @@ -1410,7 +1727,7 @@ 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:248 +#: part/templates/part/sale_prices.html:85 templates/js/bom.js:234 msgid "Price" msgstr "Preis" @@ -1439,8 +1756,8 @@ 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:153 -#: templates/js/part.js:411 templates/js/stock.js:453 templates/navbar.html:22 +#: 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" msgstr "Lagerbestand" @@ -1523,7 +1840,7 @@ msgstr "Firma gelöscht" msgid "Edit Supplier Part" msgstr "Zuliefererteil bearbeiten" -#: company/views.py:278 templates/js/stock.js:799 +#: company/views.py:278 templates/js/stock.js:846 msgid "Create new Supplier Part" msgstr "Neues Zuliefererteil anlegen" @@ -1531,17 +1848,17 @@ msgstr "Neues Zuliefererteil anlegen" msgid "Delete Supplier Part" msgstr "Zuliefererteil entfernen" -#: company/views.py:416 part/views.py:2287 +#: company/views.py:416 part/views.py:2298 #, fuzzy #| msgid "Add Price Break" msgid "Added new price break" msgstr "Preisstaffel hinzufügen" -#: company/views.py:453 part/views.py:2332 +#: company/views.py:453 part/views.py:2343 msgid "Edit Price Break" msgstr "Preisstaffel bearbeiten" -#: company/views.py:469 part/views.py:2348 +#: company/views.py:469 part/views.py:2359 msgid "Delete Price Break" msgstr "Preisstaffel löschen" @@ -1622,7 +1939,7 @@ msgstr "Link auf externe Seite" msgid "Order notes" msgstr "Bestell-Notizen" -#: order/models.py:140 order/models.py:318 +#: order/models.py:140 order/models.py:326 #, fuzzy #| msgid "Purchase Order Details" msgid "Purchase order status" @@ -1646,8 +1963,8 @@ msgstr "" msgid "Date order was completed" msgstr "Bestellung als vollständig markieren" -#: order/models.py:185 order/models.py:259 part/views.py:1398 -#: stock/models.py:241 stock/models.py:805 +#: order/models.py:185 order/models.py:267 part/views.py:1409 +#: stock/models.py:243 stock/models.py:816 msgid "Quantity must be greater than zero" msgstr "Anzahl muss größer Null sein" @@ -1655,69 +1972,69 @@ msgstr "Anzahl muss größer Null sein" msgid "Part supplier must match PO supplier" msgstr "Teile-Zulieferer muss dem Zulieferer des Kaufvertrags entsprechen" -#: order/models.py:254 +#: order/models.py:262 msgid "Lines can only be received against an order marked as 'Placed'" msgstr "Nur Teile aufgegebener Bestllungen können empfangen werden" -#: order/models.py:314 +#: order/models.py:322 msgid "Company to which the items are being sold" msgstr "" -#: order/models.py:320 +#: order/models.py:328 msgid "Customer order reference code" msgstr "Bestellreferenz" -#: order/models.py:359 +#: order/models.py:367 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:436 +#: order/models.py:454 msgid "Item quantity" msgstr "Anzahl" -#: order/models.py:438 +#: order/models.py:456 msgid "Line item reference" msgstr "Position - Referenz" -#: order/models.py:440 +#: order/models.py:458 msgid "Line item notes" msgstr "Position - Notizen" -#: order/models.py:466 order/templates/order/order_base.html:9 +#: order/models.py:484 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 -#: stock/templates/stock/item_base.html:252 templates/js/order.js:139 +#: stock/templates/stock/item_base.html:265 templates/js/order.js:139 msgid "Purchase Order" msgstr "Kaufvertrag" -#: order/models.py:479 +#: order/models.py:497 msgid "Supplier part" msgstr "Zulieferer-Teil" -#: order/models.py:482 +#: order/models.py:500 msgid "Number of items received" msgstr "Empfangene Objekt-Anzahl" -#: order/models.py:576 +#: order/models.py:594 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:578 +#: order/models.py:596 msgid "Cannot allocate stock to a line without a part" msgstr "Kann Lagerobjekt keiner Zeile ohne Teil hinzufügen" -#: order/models.py:581 +#: order/models.py:599 msgid "Allocation quantity cannot exceed stock quantity" msgstr "zugewiesene Anzahl darf nicht die verfügbare Anzahl überschreiten" -#: order/models.py:591 +#: order/models.py:609 msgid "Quantity must be 1 for serialized stock item" msgstr "Anzahl muss 1 für Objekte mit Seriennummer sein" -#: order/models.py:608 +#: order/models.py:626 msgid "Select stock item to allocate" msgstr "Lagerobjekt für Zuordnung auswählen" -#: order/models.py:611 +#: order/models.py:629 msgid "Enter stock allocation quantity" msgstr "Zuordnungsanzahl eingeben" @@ -1843,14 +2160,9 @@ msgstr "Position empfangen" msgid "Line Items" msgstr "Position hinzufügen" -#: order/templates/order/po_tabs.html:11 order/templates/order/so_tabs.html:16 -#: part/templates/part/tabs.html:67 stock/templates/stock/tabs.html:32 -msgid "Attachments" -msgstr "Anhänge" - #: order/templates/order/purchase_order_detail.html:17 -#: order/templates/order/sales_order_detail.html:19 order/views.py:1117 -#: order/views.py:1232 +#: order/templates/order/sales_order_detail.html:19 order/views.py:1115 +#: order/views.py:1199 msgid "Add Line Item" msgstr "Position hinzufügen" @@ -1861,7 +2173,7 @@ msgstr "Bestellpositionen" #: order/templates/order/purchase_order_detail.html:39 #: order/templates/order/purchase_order_detail.html:119 #: part/templates/part/category.html:173 part/templates/part/category.html:215 -#: templates/js/stock.js:804 +#: templates/js/stock.js:851 msgid "New Location" msgstr "Neuer Standort" @@ -1902,7 +2214,7 @@ msgid "Select parts to receive against this order" msgstr "" #: order/templates/order/receive_parts.html:21 -#: part/templates/part/part_base.html:145 templates/js/part.js:427 +#: part/templates/part/part_base.html:145 templates/js/part.js:434 msgid "On Order" msgstr "bestellt" @@ -1943,10 +2255,40 @@ msgstr "Warnung" 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 +#: stock/templates/stock/item_base.html:191 templates/js/build.js:402 +msgid "Serial Number" +msgstr "Seriennummer" + +#: order/templates/order/sales_order_detail.html:96 templates/js/build.js:443 +#: templates/js/build.js:742 +msgid "Edit stock allocation" +msgstr "Lagerobjekt-Standort bearbeiten" + +#: order/templates/order/sales_order_detail.html:97 templates/js/build.js:445 +#: templates/js/build.js:743 +msgid "Delete stock allocation" +msgstr "Zuweisung löschen" + +#: order/templates/order/sales_order_detail.html:225 +#: part/templates/part/tabs.html:23 templates/js/build.js:507 +#: templates/js/build.js:738 +msgid "Allocated" +msgstr "Zugeordnet" + #: order/templates/order/sales_order_detail.html:227 msgid "Fulfilled" msgstr "Erledigt" +#: order/templates/order/sales_order_detail.html:275 +msgid "Buy parts" +msgstr "Teile kaufen" + +#: order/templates/order/sales_order_detail.html:279 +msgid "Build parts" +msgstr "Bauteile" + #: order/templates/order/sales_order_detail.html:282 msgid "Allocate parts" msgstr "Teile zuordnen" @@ -2001,143 +2343,139 @@ msgstr "Bestellungspositionen" msgid "Add Purchase Order Attachment" msgstr "Bestellanhang hinzufügen" -#: order/views.py:109 order/views.py:157 part/views.py:92 stock/views.py:175 -msgid "Added attachment" -msgstr "Anhang hinzugefügt" - -#: order/views.py:148 +#: order/views.py:150 msgid "Add Sales Order Attachment" msgstr "Auftragsanhang hinzufügen" -#: order/views.py:184 order/views.py:206 -msgid "Edit Attachment" -msgstr "Anhang bearbeiten" - -#: order/views.py:189 order/views.py:211 -msgid "Attachment updated" -msgstr "Anhang aktualisiert" - -#: order/views.py:226 order/views.py:241 -msgid "Delete Attachment" -msgstr "Anhang löschen" - -#: order/views.py:233 order/views.py:248 stock/views.py:233 -msgid "Deleted attachment" -msgstr "Anhang gelöscht" - -#: order/views.py:301 +#: order/views.py:310 msgid "Create Purchase Order" msgstr "Bestellung anlegen" -#: order/views.py:333 +#: order/views.py:345 msgid "Create Sales Order" msgstr "Auftrag anlegen" -#: order/views.py:364 +#: order/views.py:380 msgid "Edit Purchase Order" msgstr "Bestellung bearbeiten" -#: order/views.py:385 +#: order/views.py:401 msgid "Edit Sales Order" msgstr "Auftrag bearbeiten" -#: order/views.py:402 +#: order/views.py:418 msgid "Cancel Order" msgstr "Bestellung stornieren" -#: order/views.py:418 order/views.py:451 +#: order/views.py:428 order/views.py:455 msgid "Confirm order cancellation" msgstr "Bestellstornierung bestätigen" -#: order/views.py:436 +#: order/views.py:431 order/views.py:458 +msgid "Order cannot be cancelled" +msgstr "" + +#: order/views.py:445 msgid "Cancel sales order" msgstr "Auftrag stornieren" -#: order/views.py:457 -msgid "Could not cancel order" -msgstr "Stornierung fehlgeschlagen" - -#: order/views.py:471 +#: order/views.py:472 msgid "Issue Order" msgstr "Bestellung aufgeben" -#: order/views.py:487 +#: order/views.py:482 msgid "Confirm order placement" msgstr "Bestellungstätigung bestätigen" -#: order/views.py:508 +#: order/views.py:492 +#, fuzzy +#| msgid "Purchase Order Details" +msgid "Purchase order issued" +msgstr "Bestelldetails" + +#: order/views.py:503 msgid "Complete Order" msgstr "Auftrag fertigstellen" -#: order/views.py:544 +#: order/views.py:520 +#, fuzzy +#| msgid "Confirm build completion" +msgid "Confirm order completion" +msgstr "Bau-Fertigstellung bestätigen" + +#: order/views.py:531 +#, fuzzy +#| msgid "Mark order as complete" +msgid "Purchase order completed" +msgstr "Bestellung als vollständig markieren" + +#: order/views.py:541 msgid "Ship Order" msgstr "Versenden" -#: order/views.py:561 +#: order/views.py:558 msgid "Confirm order shipment" msgstr "Versand bestätigen" -#: order/views.py:567 +#: order/views.py:564 msgid "Could not ship order" msgstr "Versand fehlgeschlagen" -#: order/views.py:619 +#: order/views.py:616 msgid "Receive Parts" msgstr "Teile empfangen" -#: order/views.py:687 +#: order/views.py:684 msgid "Items received" msgstr "Anzahl empfangener Positionen" -#: order/views.py:701 +#: order/views.py:698 msgid "No destination set" msgstr "Kein Ziel gesetzt" -#: order/views.py:746 +#: order/views.py:743 msgid "Error converting quantity to number" msgstr "Fehler beim Konvertieren zu Zahl" -#: order/views.py:752 +#: order/views.py:749 msgid "Receive quantity less than zero" msgstr "Anzahl kleiner null empfangen" -#: order/views.py:758 +#: order/views.py:755 msgid "No lines specified" msgstr "Keine Zeilen angegeben" -#: order/views.py:1138 -msgid "Invalid Purchase Order" -msgstr "Ungültige Bestellung" +#: order/views.py:1125 +#, fuzzy +#| msgid "Supplier part description" +msgid "Supplier part must be specified" +msgstr "Zuliefererbeschreibung des Teils" -#: order/views.py:1146 +#: order/views.py:1131 msgid "Supplier must match for Part and Order" msgstr "Zulieferer muss zum Teil und zur Bestellung passen" -#: order/views.py:1151 -msgid "Invalid SupplierPart selection" -msgstr "Ungültige Wahl des Zulieferer-Teils" - -#: order/views.py:1284 order/views.py:1303 +#: order/views.py:1251 order/views.py:1270 msgid "Edit Line Item" msgstr "Position bearbeiten" -#: order/views.py:1320 order/views.py:1333 +#: order/views.py:1287 order/views.py:1300 msgid "Delete Line Item" msgstr "Position löschen" -#: order/views.py:1326 order/views.py:1339 +#: order/views.py:1293 order/views.py:1306 msgid "Deleted line item" msgstr "Position gelöscht" -#: order/views.py:1348 +#: order/views.py:1315 msgid "Allocate Stock to Order" msgstr "Lagerbestand dem Auftrag zuweisen" -#: order/views.py:1418 +#: order/views.py:1385 msgid "Edit Allocation Quantity" msgstr "Zuordnung bearbeiten" -#: order/views.py:1434 +#: order/views.py:1401 msgid "Remove allocation" msgstr "Zuordnung entfernen" @@ -2219,7 +2557,7 @@ msgstr "Neues Zulieferer-Teil" msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:93 part/models.py:1504 +#: part/forms.py:93 part/models.py:1582 msgid "Parent Part" msgstr "Ausgangsteil" @@ -2309,117 +2647,117 @@ msgstr "Teile-Kategorien" 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:435 +#: part/models.py:452 #, fuzzy #| msgid "No serial numbers found" msgid "Next available serial numbers are" msgstr "Keine Seriennummern gefunden" -#: part/models.py:439 +#: part/models.py:456 msgid "Next available serial number is" msgstr "" -#: part/models.py:444 +#: part/models.py:461 #, fuzzy #| msgid "Empty serial number string" msgid "Most recent serial number is" msgstr "Keine Seriennummer angegeben" -#: part/models.py:522 +#: part/models.py:539 msgid "Part must be unique for name, IPN and revision" msgstr "Namen, Teile- und Revisionsnummern müssen eindeutig sein" -#: part/models.py:537 part/templates/part/detail.html:19 +#: part/models.py:568 part/templates/part/detail.html:19 msgid "Part name" msgstr "Name des Teils" -#: part/models.py:541 +#: part/models.py:572 msgid "Is this part a template part?" msgstr "Ist dieses Teil eine Vorlage?" -#: part/models.py:550 +#: part/models.py:581 msgid "Is this part a variant of another part?" msgstr "Ist dieses Teil eine Variante eines anderen Teils?" -#: part/models.py:552 +#: part/models.py:583 msgid "Part description" msgstr "Beschreibung des Teils" -#: part/models.py:554 +#: part/models.py:585 msgid "Part keywords to improve visibility in search results" msgstr "Schlüsselworte um die Sichtbarkeit in Suchergebnissen zu verbessern" -#: part/models.py:559 +#: part/models.py:590 msgid "Part category" msgstr "Teile-Kategorie" -#: part/models.py:561 +#: part/models.py:592 msgid "Internal Part Number" msgstr "Interne Teilenummer" -#: part/models.py:563 +#: part/models.py:594 msgid "Part revision or version number" msgstr "Revisions- oder Versionsnummer" -#: part/models.py:577 +#: part/models.py:608 msgid "Where is this item normally stored?" msgstr "Wo wird dieses Teil normalerweise gelagert?" -#: part/models.py:621 +#: part/models.py:652 msgid "Default supplier part" msgstr "Standard-Zulieferer?" -#: part/models.py:624 +#: part/models.py:655 msgid "Minimum allowed stock level" msgstr "Minimal zulässiger Lagerbestand" -#: part/models.py:626 +#: part/models.py:657 msgid "Stock keeping units for this part" msgstr "Stock Keeping Units (SKU) für dieses Teil" -#: part/models.py:628 +#: part/models.py:659 msgid "Can this part be built from other parts?" msgstr "Kann dieses Teil aus anderen Teilen angefertigt werden?" -#: part/models.py:630 +#: part/models.py:661 msgid "Can this part be used to build other parts?" msgstr "Kann dieses Teil zum Bau von anderen genutzt werden?" -#: part/models.py:632 +#: part/models.py:663 msgid "Does this part have tracking for unique items?" msgstr "Hat dieses Teil Tracking für einzelne Objekte?" -#: part/models.py:634 +#: part/models.py:665 msgid "Can this part be purchased from external suppliers?" msgstr "Kann dieses Teil von externen Zulieferern gekauft werden?" -#: part/models.py:636 +#: part/models.py:667 msgid "Can this part be sold to customers?" msgstr "Kann dieses Teil an Kunden verkauft werden?" -#: part/models.py:638 +#: part/models.py:669 msgid "Is this part active?" msgstr "Ist dieses Teil aktiv?" -#: part/models.py:640 +#: part/models.py:671 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:642 +#: part/models.py:673 msgid "Part notes - supports Markdown formatting" msgstr "Bemerkungen - unterstüzt Markdown-Formatierung" -#: part/models.py:644 +#: part/models.py:675 msgid "Stored BOM checksum" msgstr "Prüfsumme der Stückliste gespeichert" -#: part/models.py:1377 +#: part/models.py:1455 #, 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:1394 +#: part/models.py:1472 #, fuzzy #| msgid "" #| "A stock item with this serial number already exists for template part " @@ -2429,116 +2767,121 @@ msgstr "" "Ein Teil mit dieser Seriennummer existiert bereits für die Teilevorlage " "{part}" -#: part/models.py:1413 templates/js/part.js:560 templates/js/stock.js:92 +#: part/models.py:1491 templates/js/part.js:567 templates/js/stock.js:92 #, fuzzy #| msgid "Instance Name" msgid "Test Name" msgstr "Instanzname" -#: part/models.py:1414 +#: part/models.py:1492 #, fuzzy #| msgid "Serial number for this item" msgid "Enter a name for the test" msgstr "Seriennummer für dieses Teil" -#: part/models.py:1419 +#: part/models.py:1497 #, fuzzy #| msgid "Description" msgid "Test Description" msgstr "Beschreibung" -#: part/models.py:1420 +#: part/models.py:1498 #, fuzzy #| msgid "Brief description of the build" msgid "Enter description for this test" msgstr "Kurze Beschreibung des Baus" -#: part/models.py:1426 +#: part/models.py:1503 templates/js/part.js:576 +#: templates/js/table_filters.js:172 +msgid "Required" +msgstr "benötigt" + +#: part/models.py:1504 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1431 templates/js/part.js:577 +#: part/models.py:1509 templates/js/part.js:584 #, fuzzy #| msgid "Required Parts" msgid "Requires Value" msgstr "benötigte Teile" -#: part/models.py:1432 +#: part/models.py:1510 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1437 templates/js/part.js:584 +#: part/models.py:1515 templates/js/part.js:591 #, fuzzy #| msgid "Delete Attachment" msgid "Requires Attachment" msgstr "Anhang löschen" -#: part/models.py:1438 +#: part/models.py:1516 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1471 +#: part/models.py:1549 msgid "Parameter template name must be unique" msgstr "Vorlagen-Name des Parameters muss eindeutig sein" -#: part/models.py:1476 +#: part/models.py:1554 msgid "Parameter Name" msgstr "Name des Parameters" -#: part/models.py:1478 +#: part/models.py:1556 msgid "Parameter Units" msgstr "Parameter Einheit" -#: part/models.py:1506 +#: part/models.py:1584 msgid "Parameter Template" msgstr "Parameter Vorlage" -#: part/models.py:1508 +#: part/models.py:1586 msgid "Parameter Value" msgstr "Parameter Wert" -#: part/models.py:1545 +#: part/models.py:1623 msgid "Select parent part" msgstr "Ausgangsteil auswählen" -#: part/models.py:1553 +#: part/models.py:1631 msgid "Select part to be used in BOM" msgstr "Teil für die Nutzung in der Stückliste auswählen" -#: part/models.py:1559 +#: part/models.py:1637 msgid "BOM quantity for this BOM item" msgstr "Stücklisten-Anzahl für dieses Stücklisten-Teil" -#: part/models.py:1561 +#: part/models.py:1639 #, fuzzy #| msgid "Confim BOM item deletion" msgid "This BOM item is optional" msgstr "Löschung von BOM-Position bestätigen" -#: part/models.py:1564 +#: part/models.py:1642 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "Geschätzter Ausschuss (absolut oder prozentual)" -#: part/models.py:1567 +#: part/models.py:1645 msgid "BOM item reference" msgstr "Referenz des Objekts auf der Stückliste" -#: part/models.py:1570 +#: part/models.py:1648 msgid "BOM item notes" msgstr "Notizen zum Stücklisten-Objekt" -#: part/models.py:1572 +#: part/models.py:1650 msgid "BOM line checksum" msgstr "Prüfsumme der Stückliste" -#: part/models.py:1636 part/views.py:1404 part/views.py:1456 -#: stock/models.py:231 +#: part/models.py:1717 part/views.py:1415 part/views.py:1467 +#: stock/models.py:233 #, 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:1645 +#: part/models.py:1733 #, fuzzy #| msgid "New BOM Item" msgid "BOM Item" @@ -2558,10 +2901,10 @@ msgstr "Bestellung" #: part/templates/part/allocation.html:28 #: part/templates/part/allocation.html:45 #: stock/templates/stock/item_base.html:8 -#: stock/templates/stock/item_base.html:58 -#: stock/templates/stock/item_base.html:260 -#: stock/templates/stock/stock_adjust.html:16 templates/js/build.js:126 -#: templates/js/stock.js:661 templates/js/stock.js:897 +#: stock/templates/stock/item_base.html:72 +#: stock/templates/stock/item_base.html:273 +#: 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" msgstr "Lagerobjekt" @@ -2573,78 +2916,78 @@ msgstr "Anhänge" msgid "Bill of Materials" msgstr "Stückliste" -#: part/templates/part/bom.html:38 +#: part/templates/part/bom.html:34 msgid "Remove selected BOM items" msgstr "Ausgewählte Stücklistenpositionen entfernen" -#: part/templates/part/bom.html:41 +#: part/templates/part/bom.html:37 msgid "Import BOM data" msgstr "Stückliste importieren" -#: part/templates/part/bom.html:42 +#: part/templates/part/bom.html:38 msgid "Import from File" msgstr "" -#: part/templates/part/bom.html:45 +#: part/templates/part/bom.html:41 msgid "Copy BOM from parent part" msgstr "" -#: part/templates/part/bom.html:46 +#: part/templates/part/bom.html:42 #, fuzzy #| msgid "Parameters" msgid "Copy from Parent" msgstr "Parameter" -#: part/templates/part/bom.html:49 +#: part/templates/part/bom.html:45 msgid "New BOM Item" msgstr "Neue Stücklistenposition" -#: part/templates/part/bom.html:50 +#: part/templates/part/bom.html:46 #, fuzzy #| msgid "Add Line Item" msgid "Add Item" msgstr "Position hinzufügen" -#: part/templates/part/bom.html:52 +#: part/templates/part/bom.html:48 msgid "Finish Editing" msgstr "Bearbeitung beenden" -#: part/templates/part/bom.html:53 +#: part/templates/part/bom.html:49 #, fuzzy #| msgid "Finish Editing" msgid "Finished" msgstr "Bearbeitung beenden" -#: part/templates/part/bom.html:57 +#: part/templates/part/bom.html:53 msgid "Edit BOM" msgstr "Stückliste bearbeiten" -#: part/templates/part/bom.html:58 part/templates/part/params.html:38 +#: part/templates/part/bom.html:54 part/templates/part/params.html:38 #: templates/InvenTree/settings/user.html:19 msgid "Edit" msgstr "Bearbeiten" -#: part/templates/part/bom.html:61 +#: part/templates/part/bom.html:57 msgid "Validate Bill of Materials" msgstr "Stückliste validieren" -#: part/templates/part/bom.html:62 +#: part/templates/part/bom.html:58 #, fuzzy #| msgid "Validate BOM" msgid "Validate" msgstr "BOM validieren" -#: part/templates/part/bom.html:66 part/views.py:1695 +#: part/templates/part/bom.html:62 part/views.py:1706 msgid "Export Bill of Materials" msgstr "Stückliste exportieren" -#: part/templates/part/bom.html:127 +#: part/templates/part/bom.html:123 #, fuzzy #| msgid "Remove selected BOM items" msgid "Delete selected BOM items?" msgstr "Ausgewählte Stücklistenpositionen entfernen" -#: part/templates/part/bom.html:128 +#: part/templates/part/bom.html:124 #, fuzzy #| msgid "Remove selected BOM items" msgid "All selected BOM items will be deleted" @@ -2746,7 +3089,7 @@ msgstr "" msgid "Part Builds" msgstr "Eltern-Bau" -#: part/templates/part/build.html:14 +#: part/templates/part/build.html:15 #, fuzzy #| msgid "Start new Build" msgid "Start New Build" @@ -2756,7 +3099,7 @@ msgstr "Neuen Bau beginnen" msgid "All parts" msgstr "Alle Teile" -#: part/templates/part/category.html:24 part/views.py:2098 +#: part/templates/part/category.html:24 part/views.py:2109 msgid "Create new part category" msgstr "Teilkategorie anlegen" @@ -2846,7 +3189,7 @@ msgstr "Teilkategorie anlegen" msgid "Create new Part Category" msgstr "Teilkategorie anlegen" -#: part/templates/part/category.html:216 stock/views.py:1343 +#: part/templates/part/category.html:216 stock/views.py:1338 msgid "Create new Stock Location" msgstr "Neuen Lager-Standort erstellen" @@ -2877,11 +3220,11 @@ msgid "Part Details" msgstr "Teile-Details" #: part/templates/part/detail.html:25 part/templates/part/part_base.html:95 -#: templates/js/part.js:141 +#: templates/js/part.js:180 msgid "IPN" msgstr "IPN (Interne Produktnummer)" -#: part/templates/part/detail.html:32 templates/js/part.js:145 +#: part/templates/part/detail.html:32 templates/js/part.js:184 msgid "Revision" msgstr "Revision" @@ -2902,7 +3245,7 @@ msgid "Variant Of" msgstr "Variante von" #: part/templates/part/detail.html:70 part/templates/part/set_category.html:15 -#: templates/js/part.js:398 +#: templates/js/part.js:405 msgid "Category" msgstr "Kategorie" @@ -2943,7 +3286,7 @@ msgid "Part is not a virtual part" msgstr "Teil ist nicht virtuell" #: part/templates/part/detail.html:148 stock/forms.py:248 -#: templates/js/table_filters.js:23 templates/js/table_filters.js:243 +#: templates/js/table_filters.js:23 templates/js/table_filters.js:248 msgid "Template" msgstr "Vorlage" @@ -2959,7 +3302,7 @@ msgstr "Teil kann keine Vorlage sein wenn es Variante eines anderen Teils ist" msgid "Part is not a template part" msgstr "Teil ist nicht virtuell" -#: part/templates/part/detail.html:158 templates/js/table_filters.js:255 +#: part/templates/part/detail.html:158 templates/js/table_filters.js:260 msgid "Assembly" msgstr "Baugruppe" @@ -2971,7 +3314,7 @@ msgstr "Teil kann aus anderen Teilen angefertigt werden" msgid "Part cannot be assembled from other parts" msgstr "Teil kann nicht aus anderen Teilen angefertigt werden" -#: part/templates/part/detail.html:168 templates/js/table_filters.js:259 +#: part/templates/part/detail.html:168 templates/js/table_filters.js:264 msgid "Component" msgstr "Komponente" @@ -2984,7 +3327,7 @@ msgid "Part cannot be used in assemblies" msgstr "Teil kann nicht in Baugruppen benutzt werden" #: part/templates/part/detail.html:178 templates/js/table_filters.js:31 -#: templates/js/table_filters.js:271 +#: templates/js/table_filters.js:276 msgid "Trackable" msgstr "nachverfolgbar" @@ -3004,7 +3347,7 @@ msgstr "Kaufbar" msgid "Part can be purchased from external suppliers" msgstr "Teil kann von externen Zulieferern gekauft werden" -#: part/templates/part/detail.html:198 templates/js/table_filters.js:267 +#: part/templates/part/detail.html:198 templates/js/table_filters.js:272 msgid "Salable" msgstr "Verkäuflich" @@ -3017,7 +3360,8 @@ msgid "Part cannot be sold to customers" msgstr "Teil kann nicht an Kunden verkauft werden" #: part/templates/part/detail.html:214 templates/js/table_filters.js:19 -#: templates/js/table_filters.js:55 templates/js/table_filters.js:238 +#: templates/js/table_filters.js:55 templates/js/table_filters.js:186 +#: templates/js/table_filters.js:243 msgid "Active" msgstr "Aktiv" @@ -3049,13 +3393,13 @@ msgstr "Parameter hinzufügen" msgid "New Parameter" msgstr "Neuer Parameter" -#: part/templates/part/params.html:25 stock/models.py:1391 +#: part/templates/part/params.html:25 stock/models.py:1415 #: templates/js/stock.js:112 msgid "Value" msgstr "Wert" #: part/templates/part/params.html:41 part/templates/part/supplier.html:19 -#: users/models.py:145 +#: users/models.py:146 msgid "Delete" msgstr "Löschen" @@ -3077,9 +3421,8 @@ msgstr "Dieses Teil ist eine Vorlage." msgid "This part is a variant of" msgstr "Dieses Teil ist eine Variante von" -#: part/templates/part/part_base.html:36 templates/js/bom.js:175 -#: templates/js/company.js:155 templates/js/part.js:133 -#: templates/js/part.js:375 +#: part/templates/part/part_base.html:36 templates/js/company.js:155 +#: templates/js/part.js:95 templates/js/part.js:172 msgid "Inactive" msgstr "Inaktiv" @@ -3088,7 +3431,7 @@ msgid "Star this part" msgstr "Teil favorisieren" #: part/templates/part/part_base.html:49 -#: stock/templates/stock/item_base.html:88 +#: stock/templates/stock/item_base.html:101 #: stock/templates/stock/location.html:29 #, fuzzy #| msgid "Source Location" @@ -3096,7 +3439,7 @@ msgid "Barcode actions" msgstr "Quell-Standort" #: part/templates/part/part_base.html:51 -#: stock/templates/stock/item_base.html:90 +#: stock/templates/stock/item_base.html:103 #: stock/templates/stock/location.html:31 #, fuzzy #| msgid "Part QR Code" @@ -3104,7 +3447,7 @@ msgid "Show QR Code" msgstr "Teil-QR-Code" #: part/templates/part/part_base.html:52 -#: stock/templates/stock/item_base.html:91 +#: stock/templates/stock/item_base.html:104 #: stock/templates/stock/location.html:32 msgid "Print Label" msgstr "" @@ -3209,8 +3552,8 @@ msgstr "Teil entfernen" msgid "Part Stock" msgstr "Teilbestand" -#: part/templates/part/stock_count.html:7 templates/js/bom.js:238 -#: templates/js/part.js:435 +#: part/templates/part/stock_count.html:7 templates/js/bom.js:224 +#: templates/js/part.js:442 msgid "No Stock" msgstr "Kein Bestand" @@ -3258,7 +3601,7 @@ msgstr "Stückliste" msgid "Used In" msgstr "Benutzt in" -#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:304 +#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:317 msgid "Tests" msgstr "" @@ -3292,198 +3635,214 @@ msgstr "Varianten" msgid "Add part attachment" msgstr "Teilanhang hinzufügen" -#: part/views.py:131 templates/attachment_table.html:34 +#: part/views.py:135 templates/attachment_table.html:34 msgid "Edit attachment" msgstr "Anhang bearbeiten" -#: part/views.py:137 +#: part/views.py:141 msgid "Part attachment updated" msgstr "Teilanhang aktualisiert" -#: part/views.py:152 +#: part/views.py:156 msgid "Delete Part Attachment" msgstr "Teilanhang löschen" -#: part/views.py:160 +#: part/views.py:164 msgid "Deleted part attachment" msgstr "Teilanhang gelöscht" -#: part/views.py:169 +#: part/views.py:173 #, fuzzy #| msgid "Create Part Parameter Template" msgid "Create Test Template" msgstr "Teilparametervorlage anlegen" -#: part/views.py:198 +#: part/views.py:202 #, fuzzy #| msgid "Edit Template" msgid "Edit Test Template" msgstr "Vorlage bearbeiten" -#: part/views.py:214 +#: part/views.py:218 #, fuzzy #| msgid "Delete Template" msgid "Delete Test Template" msgstr "Vorlage löschen" -#: part/views.py:223 +#: part/views.py:227 msgid "Set Part Category" msgstr "Teilkategorie auswählen" -#: part/views.py:273 +#: part/views.py:277 #, python-brace-format msgid "Set category for {n} parts" msgstr "Kategorie für {n} Teile setzen" -#: part/views.py:308 +#: part/views.py:312 msgid "Create Variant" msgstr "Variante anlegen" -#: part/views.py:388 +#: part/views.py:394 msgid "Duplicate Part" msgstr "Teil duplizieren" -#: part/views.py:395 +#: part/views.py:401 msgid "Copied part" msgstr "Teil kopiert" -#: part/views.py:514 templates/js/stock.js:793 +#: part/views.py:455 part/views.py:585 +msgid "Possible matches exist - confirm creation of new part" +msgstr "" + +#: part/views.py:520 templates/js/stock.js:840 msgid "Create New Part" msgstr "Neues Teil anlegen" -#: part/views.py:521 +#: part/views.py:527 msgid "Created new part" msgstr "Neues Teil angelegt" -#: part/views.py:736 +#: part/views.py:743 msgid "Part QR Code" msgstr "Teil-QR-Code" -#: part/views.py:755 +#: part/views.py:762 msgid "Upload Part Image" msgstr "Teilbild hochladen" -#: part/views.py:763 part/views.py:800 +#: part/views.py:770 part/views.py:807 msgid "Updated part image" msgstr "Teilbild aktualisiert" -#: part/views.py:772 +#: part/views.py:779 msgid "Select Part Image" msgstr "Teilbild auswählen" -#: part/views.py:803 +#: part/views.py:810 msgid "Part image not found" msgstr "Teilbild nicht gefunden" -#: part/views.py:814 +#: part/views.py:821 msgid "Edit Part Properties" msgstr "Teileigenschaften bearbeiten" -#: part/views.py:841 +#: part/views.py:848 #, fuzzy #| msgid "Duplicate Part" msgid "Duplicate BOM" msgstr "Teil duplizieren" -#: part/views.py:872 +#: part/views.py:879 #, fuzzy #| msgid "Confirm unallocation of build stock" msgid "Confirm duplication of BOM from parent" msgstr "Zuweisungsaufhebung bestätigen" -#: part/views.py:890 +#: part/views.py:900 msgid "Validate BOM" msgstr "BOM validieren" -#: part/views.py:1057 +#: part/views.py:923 +#, 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:934 +#, fuzzy +#| msgid "Validate Bill of Materials" +msgid "Validated Bill of Materials" +msgstr "Stückliste validieren" + +#: part/views.py:1068 msgid "No BOM file provided" msgstr "Keine Stückliste angegeben" -#: part/views.py:1407 +#: part/views.py:1418 msgid "Enter a valid quantity" msgstr "Bitte eine gültige Anzahl eingeben" -#: part/views.py:1432 part/views.py:1435 +#: part/views.py:1443 part/views.py:1446 msgid "Select valid part" msgstr "Bitte ein gültiges Teil auswählen" -#: part/views.py:1441 +#: part/views.py:1452 msgid "Duplicate part selected" msgstr "Teil doppelt ausgewählt" -#: part/views.py:1479 +#: part/views.py:1490 msgid "Select a part" msgstr "Teil auswählen" -#: part/views.py:1485 +#: part/views.py:1496 #, 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:1489 +#: part/views.py:1500 msgid "Specify quantity" msgstr "Anzahl angeben" -#: part/views.py:1745 +#: part/views.py:1756 msgid "Confirm Part Deletion" msgstr "Löschen des Teils bestätigen" -#: part/views.py:1754 +#: part/views.py:1765 msgid "Part was deleted" msgstr "Teil wurde gelöscht" -#: part/views.py:1763 +#: part/views.py:1774 msgid "Part Pricing" msgstr "Teilbepreisung" -#: part/views.py:1889 +#: part/views.py:1900 msgid "Create Part Parameter Template" msgstr "Teilparametervorlage anlegen" -#: part/views.py:1899 +#: part/views.py:1910 msgid "Edit Part Parameter Template" msgstr "Teilparametervorlage bearbeiten" -#: part/views.py:1908 +#: part/views.py:1919 msgid "Delete Part Parameter Template" msgstr "Teilparametervorlage löschen" -#: part/views.py:1918 +#: part/views.py:1929 msgid "Create Part Parameter" msgstr "Teilparameter anlegen" -#: part/views.py:1970 +#: part/views.py:1981 msgid "Edit Part Parameter" msgstr "Teilparameter bearbeiten" -#: part/views.py:1986 +#: part/views.py:1997 msgid "Delete Part Parameter" msgstr "Teilparameter löschen" -#: part/views.py:2045 +#: part/views.py:2056 msgid "Edit Part Category" msgstr "Teilkategorie bearbeiten" -#: part/views.py:2082 +#: part/views.py:2093 msgid "Delete Part Category" msgstr "Teilkategorie löschen" -#: part/views.py:2090 +#: part/views.py:2101 msgid "Part category was deleted" msgstr "Teilekategorie wurde gelöscht" -#: part/views.py:2153 +#: part/views.py:2164 #, fuzzy #| msgid "Create BOM item" msgid "Create BOM Item" msgstr "BOM-Position anlegen" -#: part/views.py:2221 +#: part/views.py:2232 msgid "Edit BOM item" msgstr "BOM-Position beaarbeiten" -#: part/views.py:2271 +#: part/views.py:2282 msgid "Confim BOM item deletion" msgstr "Löschung von BOM-Position bestätigen" @@ -3523,6 +3882,10 @@ msgstr "" msgid "Asset file description" msgstr "Einstellungs-Beschreibung" +#: stock/forms.py:111 +msgid "Enter unique serial numbers (or leave blank)" +msgstr "Eindeutige Seriennummern eingeben (oder leer lassen)" + #: stock/forms.py:191 msgid "Label" msgstr "" @@ -3579,12 +3942,6 @@ msgstr "Lagerbestandszuordnung bestätigen" msgid "Confirm removal of installed stock items" msgstr "Bewegung der Lagerobjekte bestätigen" -#: stock/forms.py:364 -#, fuzzy -#| msgid "Description" -msgid "Destination" -msgstr "Beschreibung" - #: stock/forms.py:364 msgid "Destination stock location" msgstr "Ziel-Lagerbestand" @@ -3593,7 +3950,7 @@ msgstr "Ziel-Lagerbestand" msgid "Add note (required)" msgstr "" -#: stock/forms.py:370 stock/views.py:921 stock/views.py:1119 +#: stock/forms.py:370 stock/views.py:916 stock/views.py:1114 msgid "Confirm stock adjustment" msgstr "Bestands-Anpassung bestätigen" @@ -3611,258 +3968,260 @@ msgstr "Standard-Lagerort" 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:212 +#: stock/models.py:178 +#, fuzzy +#| msgid "Created new stock item" +msgid "Created stock item" +msgstr "Neues Lagerobjekt erstellt" + +#: stock/models.py:214 #, 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:248 +#: stock/models.py:250 #, python-brace-format msgid "Part type ('{pf}') must be {pe}" msgstr "Teile-Typ ('{pf}') muss {pe} sein" -#: stock/models.py:258 stock/models.py:267 +#: stock/models.py:260 stock/models.py:269 msgid "Quantity must be 1 for item with a serial number" msgstr "Anzahl muss für Objekte mit Seriennummer \"1\" sein" -#: stock/models.py:259 +#: stock/models.py:261 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:281 +#: stock/models.py:283 msgid "Item cannot belong to itself" msgstr "Teil kann nicht zu sich selbst gehören" -#: stock/models.py:287 +#: stock/models.py:289 msgid "Item must have a build reference if is_building=True" msgstr "" -#: stock/models.py:294 +#: stock/models.py:296 msgid "Build reference does not point to the same part object" msgstr "" -#: stock/models.py:327 +#: stock/models.py:329 msgid "Parent Stock Item" msgstr "Eltern-Lagerobjekt" -#: stock/models.py:336 +#: stock/models.py:338 msgid "Base part" msgstr "Basis-Teil" -#: stock/models.py:345 +#: stock/models.py:347 msgid "Select a matching supplier part for this stock item" msgstr "Passenden Zulieferer für dieses Lagerobjekt auswählen" -#: stock/models.py:350 stock/templates/stock/stock_app_base.html:7 +#: stock/models.py:352 stock/templates/stock/stock_app_base.html:7 msgid "Stock Location" msgstr "Lagerort" -#: stock/models.py:353 +#: stock/models.py:355 msgid "Where is this stock item located?" msgstr "Wo wird dieses Teil normalerweise gelagert?" -#: stock/models.py:358 stock/templates/stock/item_base.html:199 +#: stock/models.py:360 stock/templates/stock/item_base.html:212 msgid "Installed In" msgstr "Installiert in" -#: stock/models.py:361 +#: stock/models.py:363 msgid "Is this item installed in another item?" msgstr "Ist dieses Teil in einem anderen verbaut?" -#: stock/models.py:377 +#: stock/models.py:379 msgid "Serial number for this item" msgstr "Seriennummer für dieses Teil" -#: stock/models.py:389 +#: stock/models.py:391 msgid "Batch code for this stock item" msgstr "Losnummer für dieses Lagerobjekt" -#: stock/models.py:393 +#: stock/models.py:395 msgid "Stock Quantity" msgstr "Bestand" -#: stock/models.py:402 +#: stock/models.py:404 msgid "Source Build" msgstr "Quellbau" -#: stock/models.py:404 +#: stock/models.py:406 msgid "Build for this stock item" msgstr "Bau für dieses Lagerobjekt" -#: stock/models.py:415 +#: stock/models.py:417 msgid "Source Purchase Order" msgstr "Quellbestellung" -#: stock/models.py:418 +#: stock/models.py:420 msgid "Purchase order for this stock item" msgstr "Bestellung für dieses Teil" -#: stock/models.py:424 +#: stock/models.py:426 msgid "Destination Sales Order" msgstr "Zielauftrag" -#: stock/models.py:431 +#: stock/models.py:433 msgid "Destination Build Order" msgstr "Zielbauauftrag" -#: stock/models.py:444 +#: stock/models.py:446 msgid "Delete this Stock Item when stock is depleted" msgstr "Objekt löschen wenn Lagerbestand aufgebraucht" -#: stock/models.py:454 stock/templates/stock/item_notes.html:14 +#: stock/models.py:456 stock/templates/stock/item_notes.html:14 #: stock/templates/stock/item_notes.html:30 msgid "Stock Item Notes" msgstr "Lagerobjekt-Notizen" -#: stock/models.py:505 +#: stock/models.py:507 #, fuzzy #| msgid "Item assigned to customer?" msgid "Assigned to Customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: stock/models.py:507 +#: stock/models.py:509 #, fuzzy #| msgid "Item assigned to customer?" msgid "Manually assigned to customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: stock/models.py:520 +#: stock/models.py:522 #, fuzzy #| msgid "Item assigned to customer?" msgid "Returned from customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: stock/models.py:522 +#: stock/models.py:524 #, fuzzy #| msgid "Create new stock location" msgid "Returned to location" msgstr "Neuen Lagerort anlegen" -#: stock/models.py:650 +#: stock/models.py:652 #, fuzzy #| msgid "Installed in Stock Item" msgid "Installed into stock item" msgstr "In Lagerobjekt installiert" -#: stock/models.py:658 +#: stock/models.py:660 #, fuzzy #| msgid "Installed in Stock Item" msgid "Installed stock item" msgstr "In Lagerobjekt installiert" -#: stock/models.py:682 +#: stock/models.py:684 #, fuzzy #| msgid "Installed in Stock Item" msgid "Uninstalled stock item" msgstr "In Lagerobjekt installiert" -#: stock/models.py:701 +#: stock/models.py:703 #, fuzzy #| msgid "Include sublocations" msgid "Uninstalled into location" msgstr "Unterlagerorte einschließen" -#: stock/models.py:796 +#: stock/models.py:807 #, fuzzy #| msgid "Part is not a virtual part" msgid "Part is not set as trackable" msgstr "Teil ist nicht virtuell" -#: stock/models.py:802 +#: stock/models.py:813 msgid "Quantity must be integer" msgstr "Anzahl muss eine Ganzzahl sein" -#: stock/models.py:808 +#: stock/models.py:819 #, 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:811 +#: stock/models.py:822 msgid "Serial numbers must be a list of integers" msgstr "Seriennummern muss eine Liste von Ganzzahlen sein" -#: stock/models.py:814 +#: stock/models.py:825 msgid "Quantity does not match serial numbers" msgstr "Anzahl stimmt nicht mit den Seriennummern überein" -#: stock/models.py:824 -msgid "Serial numbers already exist: " -msgstr "Seriennummern existieren bereits:" - -#: stock/models.py:849 +#: stock/models.py:857 msgid "Add serial number" msgstr "Seriennummer hinzufügen" -#: stock/models.py:852 +#: stock/models.py:860 #, python-brace-format msgid "Serialized {n} items" msgstr "{n} Teile serialisiert" -#: stock/models.py:963 +#: stock/models.py:971 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:1292 +#: stock/models.py:1316 msgid "Tracking entry title" msgstr "Name des Eintrags-Trackings" -#: stock/models.py:1294 +#: stock/models.py:1318 msgid "Entry notes" msgstr "Eintrags-Notizen" -#: stock/models.py:1296 +#: stock/models.py:1320 msgid "Link to external page for further information" msgstr "Link auf externe Seite für weitere Informationen" -#: stock/models.py:1356 +#: stock/models.py:1380 #, fuzzy #| msgid "Serial number for this item" msgid "Value must be provided for this test" msgstr "Seriennummer für dieses Teil" -#: stock/models.py:1362 +#: stock/models.py:1386 msgid "Attachment must be uploaded for this test" msgstr "" -#: stock/models.py:1379 +#: stock/models.py:1403 msgid "Test" msgstr "" -#: stock/models.py:1380 +#: stock/models.py:1404 #, fuzzy #| msgid "Part name" msgid "Test name" msgstr "Name des Teils" -#: stock/models.py:1385 +#: stock/models.py:1409 #, fuzzy #| msgid "Search Results" msgid "Result" msgstr "Suchergebnisse" -#: stock/models.py:1386 templates/js/table_filters.js:157 +#: stock/models.py:1410 templates/js/table_filters.js:162 msgid "Test result" msgstr "" -#: stock/models.py:1392 +#: stock/models.py:1416 msgid "Test output value" msgstr "" -#: stock/models.py:1398 +#: stock/models.py:1422 #, fuzzy #| msgid "Attachments" msgid "Attachment" msgstr "Anhänge" -#: stock/models.py:1399 +#: stock/models.py:1423 #, fuzzy #| msgid "Delete attachment" msgid "Test result attachment" msgstr "Anhang löschen" -#: stock/models.py:1405 +#: stock/models.py:1429 #, fuzzy #| msgid "Edit notes" msgid "Test notes" @@ -3887,18 +4246,28 @@ msgstr "Lagerobjekt-Notizen" #: stock/templates/stock/item_base.html:20 #, fuzzy #| msgid "This stock item does not have any child items" +msgid "This stock item is in production and cannot be edited." +msgstr "Dieses Lagerobjekt hat keine Kinder" + +#: stock/templates/stock/item_base.html:21 +msgid "Edit the stock item from the build view." +msgstr "" + +#: stock/templates/stock/item_base.html:34 +#, fuzzy +#| msgid "This stock item does not have any child items" msgid "This stock item has not passed all required tests" msgstr "Dieses Lagerobjekt hat keine Kinder" -#: stock/templates/stock/item_base.html:26 +#: stock/templates/stock/item_base.html:40 msgid "This stock item is allocated to Sales Order" msgstr "Dieses Lagerobjekt ist dem Auftrag zugewiesen" -#: stock/templates/stock/item_base.html:32 +#: stock/templates/stock/item_base.html:46 msgid "This stock item is allocated to Build" msgstr "Dieses Lagerobjekt ist dem Bau zugewiesen" -#: stock/templates/stock/item_base.html:38 +#: stock/templates/stock/item_base.html:52 msgid "" "This stock item is serialized - it has a unique serial number and the " "quantity cannot be adjusted." @@ -3906,143 +4275,148 @@ msgstr "" "Dieses Lagerobjekt ist serialisiert. Es hat eine eindeutige Seriennummer und " "die Anzahl kann nicht angepasst werden." -#: stock/templates/stock/item_base.html:42 +#: stock/templates/stock/item_base.html:56 msgid "This stock item cannot be deleted as it has child items" msgstr "Dieses Lagerobjekt kann nicht gelöscht werden, da es Kinder besitzt" -#: stock/templates/stock/item_base.html:46 +#: stock/templates/stock/item_base.html:60 msgid "" "This stock item will be automatically deleted when all stock is depleted." msgstr "" "Dieses Lagerobjekt wird automatisch gelöscht wenn der Lagerbestand " "aufgebraucht ist." -#: stock/templates/stock/item_base.html:94 templates/js/barcode.js:283 +#: stock/templates/stock/item_base.html:107 templates/js/barcode.js:283 #: templates/js/barcode.js:288 msgid "Unlink Barcode" msgstr "" -#: stock/templates/stock/item_base.html:96 +#: stock/templates/stock/item_base.html:109 msgid "Link Barcode" msgstr "" -#: stock/templates/stock/item_base.html:104 +#: stock/templates/stock/item_base.html:117 #, fuzzy #| msgid "Confirm stock adjustment" msgid "Stock adjustment actions" msgstr "Bestands-Anpassung bestätigen" -#: stock/templates/stock/item_base.html:108 +#: stock/templates/stock/item_base.html:121 #: stock/templates/stock/location.html:41 templates/stock_table.html:23 msgid "Count stock" msgstr "Bestand zählen" -#: stock/templates/stock/item_base.html:109 templates/stock_table.html:21 +#: stock/templates/stock/item_base.html:122 templates/stock_table.html:21 msgid "Add stock" msgstr "Bestand hinzufügen" -#: stock/templates/stock/item_base.html:110 templates/stock_table.html:22 +#: stock/templates/stock/item_base.html:123 templates/stock_table.html:22 msgid "Remove stock" msgstr "Bestand entfernen" -#: stock/templates/stock/item_base.html:112 +#: stock/templates/stock/item_base.html:125 #, fuzzy #| msgid "Order stock" msgid "Transfer stock" msgstr "Bestand bestellen" -#: stock/templates/stock/item_base.html:114 +#: stock/templates/stock/item_base.html:127 #, fuzzy #| msgid "Serialize Stock" msgid "Serialize stock" msgstr "Lagerbestand erfassen" -#: stock/templates/stock/item_base.html:118 +#: stock/templates/stock/item_base.html:131 #, fuzzy #| msgid "Item assigned to customer?" msgid "Assign to customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: stock/templates/stock/item_base.html:121 +#: stock/templates/stock/item_base.html:134 #, fuzzy #| msgid "Count stock" msgid "Return to stock" msgstr "Bestand zählen" -#: stock/templates/stock/item_base.html:125 templates/js/stock.js:934 +#: stock/templates/stock/item_base.html:138 templates/js/stock.js:981 #, fuzzy #| msgid "Installed in Stock Item" msgid "Uninstall stock item" msgstr "In Lagerobjekt installiert" -#: stock/templates/stock/item_base.html:125 +#: stock/templates/stock/item_base.html:138 msgid "Uninstall" msgstr "" -#: stock/templates/stock/item_base.html:134 +#: stock/templates/stock/item_base.html:147 #: stock/templates/stock/location.html:38 #, fuzzy #| msgid "Stock Locations" msgid "Stock actions" msgstr "Lagerobjekt-Standorte" -#: stock/templates/stock/item_base.html:137 +#: stock/templates/stock/item_base.html:150 #, fuzzy #| msgid "Count stock items" msgid "Convert to variant" msgstr "Lagerobjekte zählen" -#: stock/templates/stock/item_base.html:140 +#: stock/templates/stock/item_base.html:153 #, fuzzy #| msgid "Count stock items" msgid "Duplicate stock item" msgstr "Lagerobjekte zählen" -#: stock/templates/stock/item_base.html:142 +#: stock/templates/stock/item_base.html:155 #, fuzzy #| msgid "Edit Stock Item" msgid "Edit stock item" msgstr "Lagerobjekt bearbeiten" -#: stock/templates/stock/item_base.html:145 +#: stock/templates/stock/item_base.html:158 #, fuzzy #| msgid "Delete Stock Item" msgid "Delete stock item" msgstr "Lagerobjekt löschen" -#: stock/templates/stock/item_base.html:151 +#: stock/templates/stock/item_base.html:164 msgid "Generate test report" msgstr "" -#: stock/templates/stock/item_base.html:159 +#: stock/templates/stock/item_base.html:172 msgid "Stock Item Details" msgstr "Lagerbestands-Details" -#: stock/templates/stock/item_base.html:224 +#: stock/templates/stock/item_base.html:237 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:231 +#: stock/templates/stock/item_base.html:244 #, fuzzy #| msgid "Unique Identifier" msgid "Barcode Identifier" msgstr "Eindeutiger Bezeichner" -#: stock/templates/stock/item_base.html:259 +#: stock/templates/stock/item_base.html:258 templates/js/build.js:626 +#: templates/navbar.html:25 +msgid "Build" +msgstr "Bau" + +#: stock/templates/stock/item_base.html:272 msgid "Parent Item" msgstr "Elternposition" -#: stock/templates/stock/item_base.html:284 +#: stock/templates/stock/item_base.html:297 msgid "Last Updated" msgstr "Zuletzt aktualisiert" -#: stock/templates/stock/item_base.html:289 +#: stock/templates/stock/item_base.html:302 msgid "Last Stocktake" msgstr "Letzte Inventur" -#: stock/templates/stock/item_base.html:293 +#: stock/templates/stock/item_base.html:306 msgid "No stocktake performed" msgstr "Keine Inventur ausgeführt" @@ -4186,7 +4560,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:1315 +#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1310 #, fuzzy #| msgid "Count Stock Items" msgid "Convert Stock Item" @@ -4238,238 +4612,246 @@ msgstr "QR-Code für diesen Standort" msgid "Add Stock Item Attachment" msgstr "Anhang hinzufügen" -#: stock/views.py:209 +#: stock/views.py:210 #, fuzzy #| msgid "Edit Stock Item" msgid "Edit Stock Item Attachment" msgstr "Lagerobjekt bearbeiten" -#: stock/views.py:226 +#: stock/views.py:227 #, fuzzy #| msgid "Delete Part Attachment" msgid "Delete Stock Item Attachment" msgstr "Teilanhang löschen" -#: stock/views.py:243 +#: stock/views.py:244 #, fuzzy #| msgid "Item assigned to customer?" msgid "Assign to Customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: stock/views.py:281 +#: stock/views.py:254 +msgid "Customer must be specified" +msgstr "" + +#: stock/views.py:278 #, fuzzy #| msgid "Part Stock" msgid "Return to Stock" msgstr "Teilbestand" -#: stock/views.py:301 +#: stock/views.py:288 #, fuzzy #| msgid "Include sublocations" msgid "Specify a valid location" msgstr "Unterlagerorte einschließen" -#: stock/views.py:305 +#: stock/views.py:299 msgid "Stock item returned from customer" msgstr "" -#: stock/views.py:317 +#: stock/views.py:309 #, fuzzy #| msgid "Select valid part" msgid "Select Label Template" msgstr "Bitte ein gültiges Teil auswählen" -#: stock/views.py:340 +#: stock/views.py:332 #, fuzzy #| msgid "Select valid part" msgid "Select valid label" msgstr "Bitte ein gültiges Teil auswählen" -#: stock/views.py:404 +#: stock/views.py:396 #, fuzzy #| msgid "Delete Template" msgid "Delete All Test Data" msgstr "Vorlage löschen" -#: stock/views.py:420 +#: stock/views.py:412 #, fuzzy #| msgid "Confirm Part Deletion" msgid "Confirm test data deletion" msgstr "Löschen des Teils bestätigen" -#: stock/views.py:440 +#: stock/views.py:432 msgid "Add Test Result" msgstr "" -#: stock/views.py:478 +#: stock/views.py:473 #, fuzzy #| msgid "Edit Template" msgid "Edit Test Result" msgstr "Vorlage bearbeiten" -#: stock/views.py:496 +#: stock/views.py:491 #, fuzzy #| msgid "Delete Template" msgid "Delete Test Result" msgstr "Vorlage löschen" -#: stock/views.py:508 +#: stock/views.py:503 #, fuzzy #| msgid "Delete Template" msgid "Select Test Report Template" msgstr "Vorlage löschen" -#: stock/views.py:523 +#: stock/views.py:518 #, fuzzy #| msgid "Select valid part" msgid "Select valid template" msgstr "Bitte ein gültiges Teil auswählen" -#: stock/views.py:576 +#: stock/views.py:571 msgid "Stock Export Options" msgstr "Lagerbestandsexportoptionen" -#: stock/views.py:698 +#: stock/views.py:693 msgid "Stock Item QR Code" msgstr "Lagerobjekt-QR-Code" -#: stock/views.py:724 +#: stock/views.py:719 #, fuzzy #| msgid "Installed in Stock Item" msgid "Install Stock Item" msgstr "In Lagerobjekt installiert" -#: stock/views.py:824 +#: stock/views.py:819 #, fuzzy #| msgid "Installed in Stock Item" msgid "Uninstall Stock Items" msgstr "In Lagerobjekt installiert" -#: stock/views.py:932 +#: stock/views.py:927 #, fuzzy #| msgid "Installed in Stock Item" msgid "Uninstalled stock items" msgstr "In Lagerobjekt installiert" -#: stock/views.py:957 +#: stock/views.py:952 msgid "Adjust Stock" msgstr "Lagerbestand anpassen" -#: stock/views.py:1067 +#: stock/views.py:1062 msgid "Move Stock Items" msgstr "Lagerobjekte bewegen" -#: stock/views.py:1068 +#: stock/views.py:1063 msgid "Count Stock Items" msgstr "Lagerobjekte zählen" -#: stock/views.py:1069 +#: stock/views.py:1064 msgid "Remove From Stock" msgstr "Aus Lagerbestand entfernen" -#: stock/views.py:1070 +#: stock/views.py:1065 msgid "Add Stock Items" msgstr "Lagerobjekte hinzufügen" -#: stock/views.py:1071 +#: stock/views.py:1066 msgid "Delete Stock Items" msgstr "Lagerobjekte löschen" -#: stock/views.py:1099 +#: stock/views.py:1094 msgid "Must enter integer value" msgstr "Nur Ganzzahl eingeben" -#: stock/views.py:1104 +#: stock/views.py:1099 msgid "Quantity must be positive" msgstr "Anzahl muss positiv sein" -#: stock/views.py:1111 +#: stock/views.py:1106 #, python-brace-format msgid "Quantity must not exceed {x}" msgstr "Anzahl darf {x} nicht überschreiten" -#: stock/views.py:1190 +#: stock/views.py:1185 #, python-brace-format msgid "Added stock to {n} items" msgstr "Vorrat zu {n} Lagerobjekten hinzugefügt" -#: stock/views.py:1205 +#: stock/views.py:1200 #, python-brace-format msgid "Removed stock from {n} items" msgstr "Vorrat von {n} Lagerobjekten entfernt" -#: stock/views.py:1218 +#: stock/views.py:1213 #, python-brace-format msgid "Counted stock for {n} items" msgstr "Bestand für {n} Objekte erfasst" -#: stock/views.py:1246 +#: stock/views.py:1241 msgid "No items were moved" msgstr "Keine Lagerobjekte wurden bewegt" -#: stock/views.py:1249 +#: stock/views.py:1244 #, python-brace-format msgid "Moved {n} items to {dest}" msgstr "{n} Teile nach {dest} bewegt" -#: stock/views.py:1268 +#: stock/views.py:1263 #, python-brace-format msgid "Deleted {n} stock items" msgstr "{n} Teile im Lager gelöscht" -#: stock/views.py:1280 +#: stock/views.py:1275 msgid "Edit Stock Item" msgstr "Lagerobjekt bearbeiten" -#: stock/views.py:1365 +#: stock/views.py:1360 msgid "Serialize Stock" msgstr "Lagerbestand erfassen" -#: stock/views.py:1559 +#: stock/views.py:1454 templates/js/build.js:210 +msgid "Create new Stock Item" +msgstr "Neues Lagerobjekt hinzufügen" + +#: stock/views.py:1553 #, fuzzy #| msgid "Count stock items" msgid "Duplicate Stock Item" msgstr "Lagerobjekte zählen" -#: stock/views.py:1625 +#: stock/views.py:1619 msgid "Invalid quantity" msgstr "Ungültige Menge" -#: stock/views.py:1628 +#: stock/views.py:1622 #, 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:1632 +#: stock/views.py:1626 msgid "Invalid part selection" msgstr "Ungültige Teileauswahl" -#: stock/views.py:1681 +#: stock/views.py:1674 #, python-brace-format msgid "Created {n} new stock items" msgstr "{n} neue Lagerobjekte erstellt" -#: stock/views.py:1700 stock/views.py:1716 +#: stock/views.py:1693 stock/views.py:1709 msgid "Created new stock item" msgstr "Neues Lagerobjekt erstellt" -#: stock/views.py:1735 +#: stock/views.py:1728 msgid "Delete Stock Location" msgstr "Standort löschen" -#: stock/views.py:1749 +#: stock/views.py:1742 msgid "Delete Stock Item" msgstr "Lagerobjekt löschen" -#: stock/views.py:1761 +#: stock/views.py:1754 msgid "Delete Stock Tracking Entry" msgstr "Lagerbestands-Tracking-Eintrag löschen" -#: stock/views.py:1780 +#: stock/views.py:1773 msgid "Edit Stock Tracking Entry" msgstr "Lagerbestands-Tracking-Eintrag bearbeiten" -#: stock/views.py:1790 +#: stock/views.py:1783 msgid "Add Stock Tracking Entry" msgstr "Lagerbestands-Tracking-Eintrag hinzufügen" @@ -4541,13 +4923,13 @@ msgstr "Keine Ergebnisse gefunden" msgid "Enter a search query" msgstr "Auftrag stornieren" -#: templates/InvenTree/search.html:191 templates/js/stock.js:528 +#: templates/InvenTree/search.html:191 templates/js/stock.js:289 #, fuzzy #| msgid "Item assigned to customer?" msgid "Shipped to customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: templates/InvenTree/search.html:194 templates/js/stock.js:538 +#: templates/InvenTree/search.html:194 templates/js/stock.js:299 msgid "No stock location set" msgstr "Kein Lagerort gesetzt" @@ -4701,24 +5083,12 @@ msgstr "" msgid "User Information" msgstr "Keine Benutzerinformation" -#: templates/InvenTree/settings/user.html:18 -#, fuzzy -#| msgid "No user information" -msgid "Edit User Information" -msgstr "Keine Benutzerinformation" - #: templates/InvenTree/settings/user.html:21 #, fuzzy #| msgid "Create new part" msgid "Change Password" msgstr "Neues Teil anlegen" -#: templates/InvenTree/settings/user.html:22 -#, fuzzy -#| msgid "Select part" -msgid "Set Password" -msgstr "Teil auswählen" - #: templates/InvenTree/settings/user.html:28 #, fuzzy #| msgid "User" @@ -4912,82 +5282,119 @@ msgstr "Vorrat zu {n} Lagerobjekten hinzugefügt" msgid "Barcode does not match Stock Item" msgstr "Neues Lagerobjekt hinzufügen" -#: templates/js/bom.js:156 templates/js/part.js:117 templates/js/part.js:344 -#, fuzzy -#| msgid "Trackable" -msgid "Trackable part" -msgstr "nachverfolgbar" - -#: templates/js/bom.js:160 templates/js/part.js:121 templates/js/part.js:348 -#, fuzzy -#| msgid "Virtual" -msgid "Virtual part" -msgstr "Virtuell" - -#: templates/js/bom.js:164 templates/js/company.js:147 templates/js/part.js:125 -#: templates/js/part.js:353 -msgid "Template part" -msgstr "Vorlagenteil" - -#: templates/js/bom.js:169 +#: templates/js/bom.js:159 msgid "Open subassembly" msgstr "Unterbaugruppe öffnen" -#: templates/js/bom.js:214 +#: templates/js/bom.js:200 #, fuzzy #| msgid "Options" msgid "Optional" msgstr "Optionen" -#: templates/js/bom.js:229 templates/js/build.js:133 -msgid "Available" -msgstr "verfügbar" - -#: templates/js/bom.js:254 +#: templates/js/bom.js:240 msgid "No pricing available" msgstr "Keine Preisinformation verfügbar" -#: templates/js/bom.js:273 +#: templates/js/bom.js:259 templates/js/build.js:555 #, fuzzy #| msgid "Options" msgid "Actions" msgstr "Optionen" -#: templates/js/bom.js:281 +#: templates/js/bom.js:267 msgid "Validate BOM Item" msgstr "BOM-Position validieren" -#: templates/js/bom.js:283 +#: templates/js/bom.js:269 msgid "This line has been validated" msgstr "Diese Position wurde validiert" -#: templates/js/bom.js:285 +#: templates/js/bom.js:271 msgid "Edit BOM Item" msgstr "BOM-Position bearbeiten" -#: templates/js/bom.js:287 +#: templates/js/bom.js:273 msgid "Delete BOM Item" msgstr "BOM-Position löschen" -#: templates/js/bom.js:507 +#: templates/js/bom.js:346 templates/js/build.js:289 +msgid "No BOM items found" +msgstr "Keine BOM-Einträge gefunden" + +#: templates/js/bom.js:491 msgid "INACTIVE" msgstr "INAKTIV" -#: templates/js/bom.js:521 +#: templates/js/bom.js:505 msgid "Uses" msgstr "" -#: templates/js/bom.js:532 +#: templates/js/bom.js:516 #, fuzzy #| msgid "No matching action found" msgid "No matching parts found" msgstr "Keine passende Aktion gefunden" -#: templates/js/build.js:24 +#: templates/js/build.js:56 +#, fuzzy +#| msgid "Installed in Stock Item" +msgid "Auto-allocate stock items to this output" +msgstr "In Lagerobjekt installiert" + +#: templates/js/build.js:62 +#, fuzzy +#| msgid "Complete Build" +msgid "Complete build output" +msgstr "Bau fertigstellen" + +#: templates/js/build.js:71 +#, fuzzy +#| msgid "Allocate Stock to Build" +msgid "Unallocate stock from build output" +msgstr "Lagerbestand dem Bau zuweisen" + +#: templates/js/build.js:77 +#, fuzzy +#| msgid "Delete Build" +msgid "Delete build output" +msgstr "Bau entfernt" + +#: templates/js/build.js:209 templates/stock_table.html:13 +msgid "New Stock Item" +msgstr "Neues Lagerobjekt" + +#: templates/js/build.js:477 +#, fuzzy +#| msgid "Required" +msgid "Required Part" +msgstr "benötigt" + +#: templates/js/build.js:498 +#, fuzzy +#| msgid "Quantity" +msgid "Quantity Per" +msgstr "Anzahl" + +#: templates/js/build.js:562 +#, fuzzy +#| msgid "Builds" +msgid "Build stock" +msgstr "Baue" + +#: templates/js/build.js:566 templates/stock_table.html:25 +msgid "Order stock" +msgstr "Bestand bestellen" + +#: templates/js/build.js:569 +msgid "Allocate stock" +msgstr "Lagerbestand zuweisen" + +#: templates/js/build.js:610 msgid "No builds matching query" msgstr "Keine Baue passen zur Anfrage" -#: templates/js/build.js:122 +#: templates/js/build.js:720 msgid "No parts allocated for" msgstr "Keine Teile zugeordnet zu" @@ -5011,8 +5418,11 @@ msgstr "Keine Firmeninformation gefunden" msgid "No supplier parts found" msgstr "Keine Zuliefererteile gefunden" -#: templates/js/company.js:151 templates/js/part.js:129 -#: templates/js/part.js:357 +#: templates/js/company.js:147 templates/js/part.js:79 templates/js/part.js:164 +msgid "Template part" +msgstr "Vorlagenteil" + +#: templates/js/company.js:151 templates/js/part.js:83 templates/js/part.js:168 msgid "Assembled part" msgstr "Baugruppe" @@ -5024,7 +5434,7 @@ msgstr "Link" msgid "No purchase orders found" msgstr "Keine Bestellungen gefunden" -#: templates/js/order.js:180 templates/js/stock.js:643 +#: templates/js/order.js:180 templates/js/stock.js:677 msgid "Date" msgstr "Datum" @@ -5036,67 +5446,80 @@ msgstr "Keine Aufträge gefunden" msgid "Shipment Date" msgstr "Versanddatum" -#: templates/js/part.js:166 +#: templates/js/part.js:71 templates/js/part.js:156 +#, fuzzy +#| msgid "Trackable" +msgid "Trackable part" +msgstr "nachverfolgbar" + +#: templates/js/part.js:75 templates/js/part.js:160 +#, fuzzy +#| msgid "Virtual" +msgid "Virtual part" +msgstr "Virtuell" + +#: templates/js/part.js:87 +msgid "Starred part" +msgstr "Favoritenteil" + +#: templates/js/part.js:91 +msgid "Salable part" +msgstr "Verkäufliches Teil" + +#: templates/js/part.js:205 #, fuzzy #| msgid "No parts found" msgid "No variants found" msgstr "Keine Teile gefunden" -#: templates/js/part.js:252 templates/js/part.js:450 +#: templates/js/part.js:291 templates/js/part.js:457 msgid "No parts found" msgstr "Keine Teile gefunden" -#: templates/js/part.js:304 templates/js/stock.js:409 templates/js/stock.js:966 +#: templates/js/part.js:343 templates/js/stock.js:456 +#: templates/js/stock.js:1013 msgid "Select" msgstr "Auswählen" -#: templates/js/part.js:361 -msgid "Starred part" -msgstr "Favoritenteil" - -#: templates/js/part.js:365 -msgid "Salable part" -msgstr "Verkäufliches Teil" - -#: templates/js/part.js:404 +#: templates/js/part.js:411 msgid "No category" msgstr "Keine Kategorie" -#: templates/js/part.js:422 templates/js/table_filters.js:251 +#: templates/js/part.js:429 templates/js/table_filters.js:256 msgid "Low stock" msgstr "Bestand niedrig" -#: templates/js/part.js:431 +#: templates/js/part.js:438 msgid "Building" msgstr "Im Bau" -#: templates/js/part.js:510 +#: templates/js/part.js:517 msgid "YES" msgstr "" -#: templates/js/part.js:512 +#: templates/js/part.js:519 msgid "NO" msgstr "" -#: templates/js/part.js:546 +#: templates/js/part.js:553 #, fuzzy #| msgid "No stock items matching query" msgid "No test templates matching query" msgstr "Keine zur Anfrage passenden Lagerobjekte" -#: templates/js/part.js:597 templates/js/stock.js:63 +#: templates/js/part.js:604 templates/js/stock.js:63 #, fuzzy #| msgid "Edit Sales Order" msgid "Edit test result" msgstr "Auftrag bearbeiten" -#: templates/js/part.js:598 templates/js/stock.js:64 +#: templates/js/part.js:605 templates/js/stock.js:64 #, fuzzy #| msgid "Delete attachment" msgid "Delete test result" msgstr "Anhang löschen" -#: templates/js/part.js:604 +#: templates/js/part.js:611 msgid "This test is defined for a parent part" msgstr "" @@ -5130,97 +5553,101 @@ msgstr "Keine Ergebnisse gefunden" msgid "Test Date" msgstr "Versanddatum" -#: templates/js/stock.js:263 +#: templates/js/stock.js:281 +msgid "In production" +msgstr "" + +#: templates/js/stock.js:285 +#, fuzzy +#| msgid "Installed in Stock Item" +msgid "Installed in Stock Item" +msgstr "In Lagerobjekt installiert" + +#: templates/js/stock.js:293 +#, fuzzy +#| msgid "Item assigned to customer?" +msgid "Assigned to Sales Order" +msgstr "Ist dieses Objekt einem Kunden zugeteilt?" + +#: templates/js/stock.js:313 msgid "No stock items matching query" msgstr "Keine zur Anfrage passenden Lagerobjekte" -#: templates/js/stock.js:361 templates/js/stock.js:376 +#: templates/js/stock.js:424 #, fuzzy #| msgid "Include sublocations" msgid "Undefined location" msgstr "Unterlagerorte einschließen" -#: templates/js/stock.js:469 +#: templates/js/stock.js:518 +#, fuzzy +#| msgid "StockItem is lost" +msgid "Stock item is in production" +msgstr "Lagerobjekt verloren" + +#: templates/js/stock.js:523 +#, fuzzy +#| msgid "This stock item is allocated to Sales Order" +msgid "Stock item assigned to sales order" +msgstr "Dieses Lagerobjekt ist dem Auftrag zugewiesen" + +#: templates/js/stock.js:526 +#, fuzzy +#| msgid "StockItem has been allocated" +msgid "Stock item assigned to customer" +msgstr "Lagerobjekt wurde zugewiesen" + +#: templates/js/stock.js:530 #, fuzzy #| msgid "StockItem has been allocated" msgid "Stock item has been allocated" msgstr "Lagerobjekt wurde zugewiesen" -#: templates/js/stock.js:473 -#, fuzzy -#| msgid "StockItem has been allocated" -msgid "Stock item has been assigned to customer" -msgstr "Lagerobjekt wurde zugewiesen" - -#: templates/js/stock.js:476 -#, fuzzy -#| msgid "This stock item is allocated to Sales Order" -msgid "Stock item was assigned to a build order" -msgstr "Dieses Lagerobjekt ist dem Auftrag zugewiesen" - -#: templates/js/stock.js:478 -#, fuzzy -#| msgid "This stock item is allocated to Sales Order" -msgid "Stock item was assigned to a sales order" -msgstr "Dieses Lagerobjekt ist dem Auftrag zugewiesen" - -#: templates/js/stock.js:483 +#: templates/js/stock.js:534 #, fuzzy #| msgid "Is this item installed in another item?" msgid "Stock item has been installed in another item" msgstr "Ist dieses Teil in einem anderen verbaut?" -#: templates/js/stock.js:490 +#: templates/js/stock.js:541 #, fuzzy #| msgid "StockItem has been allocated" msgid "Stock item has been rejected" msgstr "Lagerobjekt wurde zugewiesen" -#: templates/js/stock.js:494 +#: templates/js/stock.js:545 #, fuzzy #| msgid "StockItem is lost" msgid "Stock item is lost" msgstr "Lagerobjekt verloren" -#: templates/js/stock.js:498 templates/js/table_filters.js:106 +#: templates/js/stock.js:549 templates/js/table_filters.js:106 #, fuzzy #| msgid "Delete" msgid "Depleted" msgstr "Löschen" -#: templates/js/stock.js:523 -#, fuzzy -#| msgid "Installed in Stock Item" -msgid "Installed in Stock Item " -msgstr "In Lagerobjekt installiert" - -#: templates/js/stock.js:531 -#, fuzzy -#| msgid "Item assigned to customer?" -msgid "Assigned to sales order" -msgstr "Ist dieses Objekt einem Kunden zugeteilt?" - -#: templates/js/stock.js:709 +#: templates/js/stock.js:743 msgid "No user information" msgstr "Keine Benutzerinformation" -#: templates/js/stock.js:805 +#: templates/js/stock.js:852 msgid "Create New Location" msgstr "Neuen Standort anlegen" -#: templates/js/stock.js:904 +#: templates/js/stock.js:951 #, fuzzy #| msgid "Serial Number" msgid "Serial" msgstr "Seriennummer" -#: templates/js/stock.js:997 templates/js/table_filters.js:116 +#: templates/js/stock.js:1044 templates/js/table_filters.js:121 #, fuzzy #| msgid "Installed In" msgid "Installed" msgstr "Installiert in" -#: templates/js/stock.js:1022 +#: templates/js/stock.js:1069 #, fuzzy #| msgid "Installed In" msgid "Install item" @@ -5238,50 +5665,50 @@ msgstr "nachverfolgbar" msgid "Validated" msgstr "BOM validieren" -#: templates/js/table_filters.js:65 templates/js/table_filters.js:126 +#: templates/js/table_filters.js:65 templates/js/table_filters.js:131 #, fuzzy #| msgid "Serialize Stock" msgid "Is Serialized" msgstr "Lagerbestand erfassen" -#: templates/js/table_filters.js:68 templates/js/table_filters.js:133 +#: templates/js/table_filters.js:68 templates/js/table_filters.js:138 #, fuzzy #| msgid "Serial Number" msgid "Serial number GTE" msgstr "Seriennummer" -#: templates/js/table_filters.js:69 templates/js/table_filters.js:134 +#: templates/js/table_filters.js:69 templates/js/table_filters.js:139 #, fuzzy #| msgid "Serial number for this item" msgid "Serial number greater than or equal to" msgstr "Seriennummer für dieses Teil" -#: templates/js/table_filters.js:72 templates/js/table_filters.js:137 +#: templates/js/table_filters.js:72 templates/js/table_filters.js:142 #, fuzzy #| msgid "Serial Number" msgid "Serial number LTE" msgstr "Seriennummer" -#: templates/js/table_filters.js:73 templates/js/table_filters.js:138 +#: templates/js/table_filters.js:73 templates/js/table_filters.js:143 #, fuzzy #| msgid "Serial numbers already exist: " msgid "Serial number less than or equal to" msgstr "Seriennummern existieren bereits:" #: templates/js/table_filters.js:76 templates/js/table_filters.js:77 -#: templates/js/table_filters.js:129 templates/js/table_filters.js:130 +#: templates/js/table_filters.js:134 templates/js/table_filters.js:135 #, fuzzy #| msgid "Serial Number" msgid "Serial number" msgstr "Seriennummer" -#: templates/js/table_filters.js:81 templates/js/table_filters.js:147 +#: templates/js/table_filters.js:81 templates/js/table_filters.js:152 #, fuzzy #| msgid "Batch Code" msgid "Batch code" msgstr "Losnummer" -#: templates/js/table_filters.js:91 templates/js/table_filters.js:218 +#: templates/js/table_filters.js:91 templates/js/table_filters.js:223 msgid "Active parts" msgstr "Aktive Teile" @@ -5315,74 +5742,98 @@ msgstr "Objekt löschen wenn Lagerbestand aufgebraucht" msgid "Show items which are in stock" msgstr "" +#: templates/js/table_filters.js:116 +msgid "In Production" +msgstr "" + #: templates/js/table_filters.js:117 #, fuzzy +#| msgid "Delete this Stock Item when stock is depleted" +msgid "Show items which are in production" +msgstr "Objekt löschen wenn Lagerbestand aufgebraucht" + +#: templates/js/table_filters.js:122 +#, fuzzy #| msgid "Is this item installed in another item?" msgid "Show stock items which are installed in another item" msgstr "Ist dieses Teil in einem anderen verbaut?" -#: templates/js/table_filters.js:121 +#: templates/js/table_filters.js:126 #, fuzzy #| msgid "Item assigned to customer?" msgid "Sent to customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: templates/js/table_filters.js:122 +#: templates/js/table_filters.js:127 msgid "Show items which have been assigned to a customer" msgstr "" -#: templates/js/table_filters.js:142 templates/js/table_filters.js:143 +#: templates/js/table_filters.js:147 templates/js/table_filters.js:148 msgid "Stock status" msgstr "Bestandsstatus" -#: templates/js/table_filters.js:176 +#: templates/js/table_filters.js:181 msgid "Build status" msgstr "Bau-Status" -#: templates/js/table_filters.js:191 templates/js/table_filters.js:204 +#: templates/js/table_filters.js:196 templates/js/table_filters.js:209 msgid "Order status" msgstr "Bestellstatus" -#: templates/js/table_filters.js:196 templates/js/table_filters.js:209 +#: templates/js/table_filters.js:201 templates/js/table_filters.js:214 #, fuzzy #| msgid "Cascading" msgid "Outstanding" msgstr "Kaskadierend" -#: templates/js/table_filters.js:228 +#: templates/js/table_filters.js:233 msgid "Include subcategories" msgstr "Unterkategorien einschließen" -#: templates/js/table_filters.js:229 +#: templates/js/table_filters.js:234 msgid "Include parts in subcategories" msgstr "Teile in Unterkategorien einschließen" -#: templates/js/table_filters.js:233 +#: templates/js/table_filters.js:238 msgid "Has IPN" msgstr "" -#: templates/js/table_filters.js:234 +#: templates/js/table_filters.js:239 #, fuzzy #| msgid "Internal Part Number" msgid "Part has internal part number" msgstr "Interne Teilenummer" -#: templates/js/table_filters.js:239 +#: templates/js/table_filters.js:244 msgid "Show active parts" msgstr "Aktive Teile anzeigen" -#: templates/js/table_filters.js:247 +#: templates/js/table_filters.js:252 msgid "Stock available" msgstr "Bestand verfügbar" -#: templates/js/table_filters.js:263 +#: templates/js/table_filters.js:268 msgid "Starred" msgstr "Favorit" -#: templates/js/table_filters.js:275 +#: templates/js/table_filters.js:280 msgid "Purchasable" msgstr "Käuflich" +#: templates/modals.html:13 templates/modals.html:35 +msgid "Form errors exist" +msgstr "" + +#: templates/modals.html:18 templates/modals.html:40 +msgid "Close" +msgstr "" + +#: templates/modals.html:19 templates/modals.html:41 +#, fuzzy +#| msgid "Edit BOM" +msgid "Submit" +msgstr "Stückliste bearbeiten" + #: templates/navbar.html:29 msgid "Buy" msgstr "Kaufen" @@ -5459,10 +5910,6 @@ msgstr "Bestand bewegen" msgid "Order selected items" msgstr "Ausgewählte Stücklistenpositionen entfernen" -#: templates/stock_table.html:25 -msgid "Order stock" -msgstr "Bestand bestellen" - #: templates/stock_table.html:28 #, fuzzy #| msgid "Delete line item" @@ -5501,46 +5948,168 @@ msgstr "Revision" msgid "Important dates" msgstr "Stückliste importieren" -#: users/models.py:128 +#: users/models.py:129 msgid "Permission set" msgstr "" -#: users/models.py:136 +#: users/models.py:137 msgid "Group" msgstr "" -#: users/models.py:139 +#: users/models.py:140 msgid "View" msgstr "" -#: users/models.py:139 +#: users/models.py:140 msgid "Permission to view items" msgstr "" -#: users/models.py:141 +#: users/models.py:142 #, fuzzy #| msgid "Address" msgid "Add" msgstr "Adresse" -#: users/models.py:141 +#: users/models.py:142 msgid "Permission to add items" msgstr "" -#: users/models.py:143 +#: users/models.py:144 msgid "Change" msgstr "" -#: users/models.py:143 +#: users/models.py:144 msgid "Permissions to edit items" msgstr "" -#: users/models.py:145 +#: users/models.py:146 #, fuzzy #| msgid "Remove selected BOM items" msgid "Permission to delete items" msgstr "Ausgewählte Stücklistenpositionen entfernen" +#, fuzzy +#~| msgid "Serial Number" +#~ msgid "Serial Numbers" +#~ msgstr "Seriennummer" + +#~ msgid "Automatically allocate stock" +#~ msgstr "Lagerbestand automatisch zuweisen" + +#~ msgid "Auto Allocate" +#~ msgstr "Automatisches Zuweisen" + +#~ msgid "Unallocate" +#~ msgstr "Zuweisung aufheben" + +#~ msgid "Assigned" +#~ msgstr "Zugewiesen" + +#, fuzzy +#~| msgid "No stock items found that can be allocated to this build" +#~ msgid "" +#~ "Where the following conditions are met, stock will be automatically " +#~ "allocated to this build" +#~ msgstr "Keine Lagerobjekt gefunden, die diesem Bau zugewiesen werden können" + +#, fuzzy +#~| msgid "Part is not a virtual part" +#~ msgid "The part is not marked as trackable" +#~ msgstr "Teil ist nicht virtuell" + +#, fuzzy +#~| msgid "Installed in Stock Item" +#~ msgid "Only single stock items exists" +#~ msgstr "In Lagerobjekt installiert" + +#, fuzzy +#~| msgid "This stock item is allocated to Build" +#~ msgid "The stock item is not already allocated to this build" +#~ msgstr "Dieses Lagerobjekt ist dem Bau zugewiesen" + +#~ msgid "Warning: Build order allocation is not complete" +#~ msgstr "Warnung: Bau-Zuweisung ist unvollständig" + +#~ msgid "" +#~ "Build Order has not been fully allocated. Ensure that all Stock Items " +#~ "have been allocated to the Build" +#~ msgstr "" +#~ "Bau-Zuweisung ist unvollständig. Sicherstellen, dass alle Lagerobjekte " +#~ "dem Bau zugewiesen wurden" + +#~ msgid "The following actions will be performed:" +#~ msgstr "Die folgenden Aktionen werden ausgeführt:" + +#~ msgid "Remove allocated items from stock" +#~ msgstr "Zugewiesene Teile dem Lager entnehmen" + +#~ msgid "Add completed items to stock" +#~ msgstr "Komplettierte Teile dem Lager hinzufügen" + +#~ msgid "Enough Parts?" +#~ msgstr "Genügend Teile?" + +#~ msgid "Yes" +#~ msgstr "Ja" + +#~ msgid "No" +#~ msgstr "Nein" + +#~ msgid "No matching build found" +#~ msgstr "Kein passender Bau gefunden" + +#~ msgid "Check the confirmation box at the bottom of the list" +#~ msgstr "Bestätigunsbox am Ende der Liste bestätigen" + +#~ msgid "Invalid location selected" +#~ msgstr "Ungültige Ortsauswahl" + +#~ msgid "The following serial numbers already exist: ({sn})" +#~ msgstr "Die folgende Seriennummer existiert bereits: ({sn})" + +#~ msgid "Build marked as COMPLETE" +#~ msgstr "Bau als FERTIG markiert" + +#, fuzzy +#~| msgid "Available" +#~ msgid "Avaialabe" +#~ msgstr "verfügbar" + +#, fuzzy +#~| msgid "Overage must be an integer value or a percentage" +#~ msgid "Build quantity must be integer value for trackable parts" +#~ msgstr "Überschuss muss eine Ganzzahl oder ein Prozentwert sein" + +#~ msgid "Parent build to which this build is allocated" +#~ msgstr "Eltern-Bau, dem dieser Bau zugewiesen ist" + +#~ msgid "" +#~ "Stock Items are selected for automatic allocation if there is only a " +#~ "single stock item available." +#~ msgstr "" +#~ "Teile werden automatisch zugewiesen, wenn nur ein Lagerobjekt verfügbar " +#~ "ist" + +#~ msgid "Title" +#~ msgstr "Titel" + +#~ msgid "Allocate new Part" +#~ msgstr "Neues Teil zuordnen" + +#~ msgid "Could not cancel order" +#~ msgstr "Stornierung fehlgeschlagen" + +#~ msgid "Invalid Purchase Order" +#~ msgstr "Ungültige Bestellung" + +#~ msgid "Invalid SupplierPart selection" +#~ msgstr "Ungültige Wahl des Zulieferer-Teils" + +#, fuzzy +#~| msgid "This stock item is allocated to Sales Order" +#~ msgid "Stock item was assigned to a build order" +#~ msgstr "Dieses Lagerobjekt ist dem Auftrag zugewiesen" + #, fuzzy #~| msgid "Template part" #~ msgid "Templat part" @@ -5659,9 +6228,6 @@ msgstr "Ausgewählte Stücklistenpositionen entfernen" #~ "Es ist kein echtes Teil, aber echte Teile können auf dieser Vorlage " #~ "basieren." -#~ msgid "Allocate Stock to Build" -#~ msgstr "Lagerbestand dem Bau zuweisen" - #~ msgid "Allocate" #~ msgstr "zuweisen" diff --git a/InvenTree/locale/en/LC_MESSAGES/django.po b/InvenTree/locale/en/LC_MESSAGES/django.po index 3f07b7337e..aacb4b74ec 100644 --- a/InvenTree/locale/en/LC_MESSAGES/django.po +++ b/InvenTree/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-10-30 04:52+0000\n" +"POT-Creation-Date: 2020-11-03 10:02+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -26,7 +26,7 @@ msgstr "" msgid "No matching action found" msgstr "" -#: InvenTree/forms.py:102 build/forms.py:49 +#: InvenTree/forms.py:102 build/forms.py:82 build/forms.py:170 msgid "Confirm" msgstr "" @@ -46,7 +46,7 @@ msgstr "" msgid "Apply Theme" msgstr "" -#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:261 +#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:269 msgid "Invalid quantity provided" msgstr "" @@ -86,12 +86,12 @@ msgstr "" msgid "File comment" msgstr "" -#: InvenTree/models.py:68 templates/js/stock.js:700 +#: InvenTree/models.py:68 templates/js/stock.js:734 msgid "User" msgstr "" #: InvenTree/models.py:106 part/templates/part/params.html:24 -#: templates/js/part.js:90 +#: templates/js/part.js:129 msgid "Name" msgstr "" @@ -116,7 +116,7 @@ msgid "Polish" msgstr "" #: InvenTree/status_codes.py:94 InvenTree/status_codes.py:135 -#: InvenTree/status_codes.py:222 templates/js/table_filters.js:181 +#: InvenTree/status_codes.py:222 msgid "Pending" msgstr "" @@ -168,10 +168,8 @@ msgstr "" msgid "Rejected" msgstr "" -#: InvenTree/status_codes.py:223 build/templates/build/allocate.html:358 -#: order/templates/order/sales_order_detail.html:225 -#: part/templates/part/tabs.html:23 templates/js/build.js:140 -msgid "Allocated" +#: InvenTree/status_codes.py:223 +msgid "Production" msgstr "" #: InvenTree/validators.py:39 @@ -204,7 +202,27 @@ msgstr "" msgid "Overage must be an integer value or a percentage" msgstr "" -#: InvenTree/views.py:752 +#: InvenTree/views.py:493 +msgid "Delete Item" +msgstr "" + +#: InvenTree/views.py:542 +msgid "Check box to confirm item deletion" +msgstr "" + +#: InvenTree/views.py:557 templates/InvenTree/settings/user.html:18 +msgid "Edit User Information" +msgstr "" + +#: InvenTree/views.py:568 templates/InvenTree/settings/user.html:22 +msgid "Set Password" +msgstr "" + +#: InvenTree/views.py:587 +msgid "Password fields must match" +msgstr "" + +#: InvenTree/views.py:757 msgid "Database Statistics" msgstr "" @@ -248,122 +266,190 @@ msgstr "" msgid "Barcode associated with StockItem" msgstr "" -#: build/forms.py:28 +#: build/forms.py:32 msgid "Build Order reference" msgstr "" -#: build/forms.py:70 -msgid "Location of completed parts" +#: 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 +#: order/templates/order/order_wizard/select_parts.html:32 +#: order/templates/order/purchase_order_detail.html:178 +#: order/templates/order/sales_order_detail.html:74 +#: 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 +#: stock/templates/stock/item_base.html:40 +#: stock/templates/stock/item_base.html:46 +#: stock/templates/stock/item_base.html:197 +#: stock/templates/stock/stock_adjust.html:18 templates/js/barcode.js:338 +#: templates/js/bom.js:189 templates/js/build.js:404 templates/js/stock.js:725 +#: templates/js/stock.js:953 +msgid "Quantity" msgstr "" -#: build/forms.py:74 +#: build/forms.py:71 +msgid "Enter quantity for build output" +msgstr "" + +#: build/forms.py:75 stock/forms.py:111 msgid "Serial numbers" msgstr "" -#: build/forms.py:76 stock/forms.py:111 -msgid "Enter unique serial numbers (or leave blank)" +#: build/forms.py:77 +msgid "Enter serial numbers for build outputs" msgstr "" -#: build/forms.py:79 +#: build/forms.py:83 +msgid "Confirm creation of build outut" +msgstr "" + +#: build/forms.py:103 +msgid "Confirm deletion of build output" +msgstr "" + +#: build/forms.py:124 +msgid "Confirm unallocation of stock" +msgstr "" + +#: build/forms.py:148 +msgid "Confirm stock allocation" +msgstr "" + +#: build/forms.py:171 +msgid "Mark build as complete" +msgstr "" + +#: build/forms.py:195 +msgid "Location of completed parts" +msgstr "" + +#: build/forms.py:200 +msgid "Confirm completion with incomplete stock allocation" +msgstr "" + +#: build/forms.py:203 msgid "Confirm build completion" msgstr "" -#: build/models.py:54 build/templates/build/build_base.html:8 +#: build/forms.py:223 build/views.py:68 +msgid "Confirm build cancellation" +msgstr "" + +#: build/forms.py:237 +msgid "Select quantity of stock to allocate" +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:214 +#: stock/templates/stock/item_base.html:227 msgid "Build Order" msgstr "" -#: build/models.py:55 build/templates/build/index.html:6 +#: 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 msgid "Build Orders" msgstr "" -#: build/models.py:77 -msgid "Build quantity must be integer value for trackable parts" -msgstr "" - -#: build/models.py:86 build/templates/build/build_base.html:73 +#: build/models.py:72 msgid "Build Order Reference" msgstr "" -#: build/models.py:87 build/templates/build/allocate.html:342 -#: order/templates/order/purchase_order_detail.html:173 templates/js/bom.js:195 +#: build/models.py:73 order/templates/order/purchase_order_detail.html:173 +#: templates/js/bom.js:181 templates/js/build.js:493 msgid "Reference" msgstr "" -#: build/models.py:94 build/templates/build/allocate.html:337 +#: build/models.py:80 build/templates/build/detail.html:19 #: company/templates/company/supplier_part_base.html:61 #: company/templates/company/supplier_part_detail.html:27 #: order/templates/order/purchase_order_detail.html:160 #: part/templates/part/detail.html:51 part/templates/part/set_category.html:14 -#: templates/InvenTree/search.html:147 templates/js/bom.js:188 -#: templates/js/bom.js:515 templates/js/build.js:56 templates/js/company.js:56 -#: templates/js/order.js:167 templates/js/order.js:249 templates/js/part.js:149 -#: templates/js/part.js:232 templates/js/part.js:384 templates/js/part.js:565 -#: templates/js/stock.js:445 templates/js/stock.js:672 +#: 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/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" msgstr "" -#: build/models.py:97 +#: build/models.py:83 msgid "Brief description of the build" msgstr "" -#: build/models.py:105 build/templates/build/build_base.html:94 +#: build/models.py:91 build/templates/build/build_base.html:94 +#: build/templates/build/detail.html:75 msgid "Parent Build" msgstr "" -#: build/models.py:106 -msgid "Parent build to which this build is allocated" +#: build/models.py:92 +msgid "BuildOrder to which this build is allocated" msgstr "" -#: build/models.py:111 build/templates/build/allocate.html:329 -#: build/templates/build/auto_allocate.html:19 -#: build/templates/build/build_base.html:78 -#: build/templates/build/detail.html:22 order/models.py:501 +#: 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 #: 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:293 #: part/templates/part/part_app_base.html:7 #: 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:500 -#: templates/js/build.js:61 templates/js/company.js:138 -#: templates/js/part.js:213 templates/js/part.js:318 templates/js/stock.js:421 -#: templates/js/stock.js:978 +#: templates/js/barcode.js:336 templates/js/bom.js:147 templates/js/bom.js:484 +#: templates/js/build.js:647 templates/js/company.js:138 +#: templates/js/part.js:252 templates/js/part.js:357 templates/js/stock.js:468 +#: templates/js/stock.js:1025 msgid "Part" msgstr "" -#: build/models.py:120 +#: build/models.py:105 msgid "Select part to build" msgstr "" -#: build/models.py:125 +#: build/models.py:110 msgid "Sales Order Reference" msgstr "" -#: build/models.py:129 +#: build/models.py:114 msgid "SalesOrder to which this build is allocated" msgstr "" -#: build/models.py:134 +#: build/models.py:119 msgid "Source Location" msgstr "" -#: build/models.py:138 +#: build/models.py:123 msgid "" "Select location to take stock from for this build (leave blank to take from " "any stock location)" msgstr "" -#: build/models.py:142 +#: build/models.py:128 +msgid "Destination Location" +msgstr "" + +#: build/models.py:132 +msgid "Select location where the completed items will be stored" +msgstr "" + +#: build/models.py:136 msgid "Build Quantity" msgstr "" +#: build/models.py:139 +msgid "Number of stock items to build" +msgstr "" + +#: build/models.py:143 +msgid "Completed items" +msgstr "" + #: build/models.py:145 -msgid "Number of parts to build" +msgid "Number of stock items which have been completed" msgstr "" #: build/models.py:149 part/templates/part/part_base.html:155 @@ -374,7 +460,7 @@ msgstr "" msgid "Build status code" msgstr "" -#: build/models.py:157 stock/models.py:387 +#: build/models.py:157 stock/models.py:389 msgid "Batch Code" msgstr "" @@ -382,15 +468,15 @@ msgstr "" msgid "Batch code for this build output" msgstr "" -#: build/models.py:176 build/templates/build/detail.html:55 +#: build/models.py:176 build/templates/build/detail.html:89 #: company/templates/company/supplier_part_base.html:68 #: company/templates/company/supplier_part_detail.html:24 #: part/templates/part/detail.html:80 part/templates/part/part_base.html:102 -#: stock/models.py:381 stock/templates/stock/item_base.html:266 +#: stock/models.py:383 stock/templates/stock/item_base.html:279 msgid "External Link" msgstr "" -#: build/models.py:177 part/models.py:565 stock/models.py:383 +#: build/models.py:177 part/models.py:596 stock/models.py:385 msgid "Link to external URL" msgstr "" @@ -398,10 +484,10 @@ 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:70 -#: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:453 -#: stock/models.py:1404 stock/templates/stock/tabs.html:26 -#: templates/js/barcode.js:391 templates/js/bom.js:264 -#: templates/js/stock.js:116 templates/js/stock.js:544 +#: 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 +#: templates/js/barcode.js:391 templates/js/bom.js:250 +#: templates/js/stock.js:116 templates/js/stock.js:578 msgid "Notes" msgstr "" @@ -409,138 +495,118 @@ msgstr "" msgid "Extra build notes" msgstr "" -#: build/models.py:520 +#: build/models.py:543 +msgid "No build output specified" +msgstr "" + +#: build/models.py:546 +msgid "Build output is already completed" +msgstr "" + +#: build/models.py:549 +msgid "Build output does not match Build Order" +msgstr "" + +#: build/models.py:620 +msgid "Completed build output" +msgstr "" + +#: build/models.py:858 +msgid "BuildItem must be unique for build, stock_item and install_into" +msgstr "" + +#: build/models.py:880 +msgid "Build item must specify a build output" +msgstr "" + +#: build/models.py:885 #, python-brace-format msgid "Selected stock item not found in BOM for part '{p}'" msgstr "" -#: build/models.py:523 +#: build/models.py:889 #, python-brace-format msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" msgstr "" -#: build/models.py:529 order/models.py:585 +#: build/models.py:896 order/models.py:603 msgid "StockItem is over-allocated" msgstr "" -#: build/models.py:532 order/models.py:588 +#: build/models.py:900 order/models.py:606 msgid "Allocation quantity must be greater than zero" msgstr "" -#: build/models.py:535 +#: build/models.py:904 msgid "Quantity must be 1 for serialized stock" msgstr "" -#: build/models.py:564 +#: build/models.py:944 msgid "Build to allocate parts" msgstr "" -#: build/models.py:571 -msgid "Stock Item to allocate to build" +#: build/models.py:951 +msgid "Source stock item" msgstr "" -#: build/models.py:584 +#: build/models.py:964 msgid "Stock quantity to allocate to build" msgstr "" -#: build/templates/build/allocate.html:17 -#: company/templates/company/detail_part.html:28 order/views.py:804 +#: build/models.py:972 +msgid "Destination stock item" +msgstr "" + +#: build/templates/build/allocate.html:14 +msgid "Incomplete Build Ouputs" +msgstr "" + +#: build/templates/build/allocate.html:20 +msgid "Build order has been completed" +msgstr "" + +#: build/templates/build/allocate.html:24 +msgid "Create new build output" +msgstr "" + +#: build/templates/build/allocate.html:25 +msgid "Create New Output" +msgstr "" + +#: build/templates/build/allocate.html:28 +msgid "Order required parts" +msgstr "" + +#: build/templates/build/allocate.html:29 +#: company/templates/company/detail_part.html:28 order/views.py:801 #: part/templates/part/category.html:125 msgid "Order Parts" msgstr "" -#: build/templates/build/allocate.html:18 -msgid "Automatically allocate stock" +#: build/templates/build/allocate.html:32 templates/js/build.js:574 +msgid "Unallocate stock" msgstr "" -#: build/templates/build/allocate.html:18 -msgid "Auto Allocate" +#: build/templates/build/allocate.html:33 build/views.py:341 build/views.py:778 +msgid "Unallocate Stock" msgstr "" -#: build/templates/build/allocate.html:19 -msgid "Unallocate" +#: build/templates/build/allocate.html:46 +msgid "Create a new build output" msgstr "" -#: build/templates/build/allocate.html:87 templates/stock_table.html:13 -msgid "New Stock Item" +#: build/templates/build/allocate.html:47 +msgid "No incomplete build outputs remain." msgstr "" -#: build/templates/build/allocate.html:88 stock/views.py:1459 -msgid "Create new Stock Item" +#: build/templates/build/allocate.html:48 +msgid "Create a new build output using the button above" msgstr "" -#: build/templates/build/allocate.html:170 -#: order/templates/order/sales_order_detail.html:72 -#: order/templates/order/sales_order_detail.html:154 stock/models.py:375 -#: stock/templates/stock/item_base.html:178 -msgid "Serial Number" -msgstr "" - -#: build/templates/build/allocate.html:172 -#: build/templates/build/auto_allocate.html:20 -#: build/templates/build/build_base.html:83 -#: build/templates/build/detail.html:27 -#: 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/sales_order_detail.html:74 -#: 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 -#: stock/templates/stock/item_base.html:26 -#: stock/templates/stock/item_base.html:32 -#: stock/templates/stock/item_base.html:184 -#: stock/templates/stock/stock_adjust.html:18 templates/js/barcode.js:338 -#: templates/js/bom.js:203 templates/js/build.js:72 templates/js/stock.js:691 -#: templates/js/stock.js:906 -msgid "Quantity" -msgstr "" - -#: build/templates/build/allocate.html:186 -#: build/templates/build/auto_allocate.html:21 stock/forms.py:336 -#: stock/templates/stock/item_base.html:220 -#: stock/templates/stock/stock_adjust.html:17 -#: templates/InvenTree/search.html:183 templates/js/barcode.js:337 -#: templates/js/stock.js:519 -msgid "Location" -msgstr "" - -#: build/templates/build/allocate.html:210 -#: order/templates/order/sales_order_detail.html:96 templates/js/build.js:144 -msgid "Edit stock allocation" -msgstr "" - -#: build/templates/build/allocate.html:211 -#: order/templates/order/sales_order_detail.html:97 templates/js/build.js:145 -msgid "Delete stock allocation" -msgstr "" - -#: build/templates/build/allocate.html:238 templates/js/bom.js:362 -msgid "No BOM items found" -msgstr "" - -#: build/templates/build/allocate.html:347 part/models.py:1425 -#: templates/js/part.js:569 templates/js/table_filters.js:167 -msgid "Required" -msgstr "" - -#: build/templates/build/allocate.html:356 -msgid "Assigned" -msgstr "" - -#: build/templates/build/allocate.html:394 -#: order/templates/order/sales_order_detail.html:275 -msgid "Buy parts" -msgstr "" - -#: build/templates/build/allocate.html:398 -#: order/templates/order/sales_order_detail.html:279 -msgid "Build parts" -msgstr "" - -#: build/templates/build/allocate.html:401 -msgid "Allocate stock" +#: build/templates/build/attachments.html:11 build/templates/build/tabs.html:17 +#: order/templates/order/po_tabs.html:11 order/templates/order/so_tabs.html:16 +#: part/templates/part/tabs.html:67 stock/templates/stock/tabs.html:32 +msgid "Attachments" msgstr "" #: build/templates/build/auto_allocate.html:9 @@ -549,19 +615,22 @@ msgstr "" #: build/templates/build/auto_allocate.html:10 msgid "" -"Stock Items are selected for automatic allocation if there is only a single " -"stock item available." +"The following stock items will be allocated to the specified build output" msgstr "" -#: build/templates/build/auto_allocate.html:11 -msgid "The following stock items will be allocated to the build:" +#: build/templates/build/auto_allocate.html:18 stock/forms.py:336 +#: stock/templates/stock/item_base.html:233 +#: 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 +msgid "Location" msgstr "" -#: build/templates/build/auto_allocate.html:40 +#: build/templates/build/auto_allocate.html:37 msgid "No stock items found that can be automatically allocated to this build" msgstr "" -#: build/templates/build/auto_allocate.html:42 +#: build/templates/build/auto_allocate.html:39 msgid "Stock items will have to be manually allocated" msgstr "" @@ -578,7 +647,7 @@ msgstr "" #: order/templates/order/order_base.html:26 #: order/templates/order/sales_order_base.html:35 #: part/templates/part/category.html:13 part/templates/part/part_base.html:32 -#: stock/templates/stock/item_base.html:76 +#: stock/templates/stock/item_base.html:90 #: stock/templates/stock/location.html:12 msgid "Admin view" msgstr "" @@ -587,7 +656,7 @@ msgstr "" msgid "Edit Build" msgstr "" -#: build/templates/build/build_base.html:50 build/views.py:190 +#: build/templates/build/build_base.html:50 msgid "Complete Build" msgstr "" @@ -595,7 +664,7 @@ msgstr "" msgid "Cancel Build" msgstr "" -#: build/templates/build/build_base.html:59 build/views.py:456 +#: build/templates/build/build_base.html:59 build/views.py:767 msgid "Delete Build" msgstr "" @@ -603,124 +672,163 @@ msgstr "" msgid "Build Details" msgstr "" -#: build/templates/build/build_base.html:88 -#: build/templates/build/detail.html:42 +#: 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:298 templates/InvenTree/search.html:175 -#: templates/js/barcode.js:42 templates/js/build.js:77 +#: stock/templates/stock/item_base.html:311 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:506 templates/js/stock.js:914 +#: templates/js/stock.js:557 templates/js/stock.js:961 msgid "Status" msgstr "" -#: build/templates/build/build_base.html:101 order/models.py:499 +#: build/templates/build/build_base.html:88 +#: build/templates/build/detail.html:62 +msgid "Progress" +msgstr "" + +#: build/templates/build/build_base.html:101 +#: build/templates/build/detail.html:82 order/models.py:517 #: 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:208 templates/js/order.js:221 +#: stock/templates/stock/item_base.html:221 templates/js/order.js:221 msgid "Sales Order" msgstr "" -#: build/templates/build/build_base.html:107 -msgid "BOM Price" -msgstr "" - -#: build/templates/build/build_base.html:112 -msgid "BOM pricing is incomplete" -msgstr "" - -#: build/templates/build/build_base.html:115 -msgid "No pricing information" -msgstr "" - #: build/templates/build/build_output.html:9 build/templates/build/tabs.html:11 msgid "Build Outputs" msgstr "" -#: build/templates/build/complete.html:6 -#: stock/templates/stock/item_base.html:245 templates/js/build.js:40 -#: templates/navbar.html:25 -msgid "Build" +#: build/templates/build/build_output_create.html:7 +msgid "The Bill of Materials contains trackable parts" msgstr "" -#: build/templates/build/complete.html:10 -msgid "Build order allocation is complete" +#: build/templates/build/build_output_create.html:8 +msgid "Build outputs must be generated individually." msgstr "" -#: build/templates/build/complete.html:14 -msgid "Warning: Build order allocation is not complete" +#: build/templates/build/build_output_create.html:9 +msgid "Multiple build outputs will be created based on the quantity specified." +msgstr "" + +#: build/templates/build/build_output_create.html:15 +msgid "Trackable parts can have serial numbers specified" +msgstr "" + +#: build/templates/build/build_output_create.html:16 +msgid "Enter serial numbers to generate multiple single build outputs" +msgstr "" + +#: build/templates/build/cancel.html:5 +msgid "Are you sure you wish to cancel this build?" +msgstr "" + +#: build/templates/build/complete.html:8 +msgid "Build can be completed" +msgstr "" + +#: build/templates/build/complete.html:12 +msgid "Build cannot be completed" msgstr "" #: build/templates/build/complete.html:15 -msgid "" -"Build Order has not been fully allocated. Ensure that all Stock Items have " -"been allocated to the Build" +msgid "Incompleted build outputs remain" msgstr "" -#: build/templates/build/complete.html:20 -msgid "The following actions will be performed:" +#: build/templates/build/complete.html:18 +msgid "Required build quantity has not been completed" msgstr "" -#: build/templates/build/complete.html:22 -msgid "Remove allocated items from stock" +#: build/templates/build/complete_output.html:9 +msgid "Stock allocation is complete" msgstr "" -#: build/templates/build/complete.html:23 -msgid "Add completed items to stock" +#: build/templates/build/complete_output.html:13 +msgid "Stock allocation is incomplete" msgstr "" -#: build/templates/build/complete.html:29 +#: build/templates/build/complete_output.html:19 +msgid "parts have not been fully allocated" +msgstr "" + +#: build/templates/build/complete_output.html:40 msgid "The following items will be created" msgstr "" -#: build/templates/build/delete_build_item.html:6 -msgid "Are you sure you want to unallocate these parts?" +#: build/templates/build/create_build_item.html:7 +msgid "Select a stock item to allocate to the selected build output" msgstr "" -#: build/templates/build/detail.html:17 -msgid "Title" +#: build/templates/build/create_build_item.html:11 +msgid "The allocated stock will be installed into the following build output:" msgstr "" -#: build/templates/build/detail.html:31 +#: build/templates/build/create_build_item.html:19 +msgid "No stock available for" +msgstr "" + +#: build/templates/build/delete_build_item.html:8 +msgid "Are you sure you want to unallocate this stock?" +msgstr "" + +#: build/templates/build/delete_build_item.html:11 +msgid "The selected stock will be unallocated from the build output" +msgstr "" + +#: build/templates/build/detail.html:33 msgid "Stock Source" msgstr "" -#: build/templates/build/detail.html:36 +#: build/templates/build/detail.html:38 msgid "Stock can be taken from any available location." msgstr "" -#: build/templates/build/detail.html:48 -#: stock/templates/stock/item_base.html:238 templates/js/stock.js:514 -#: templates/js/stock.js:921 templates/js/table_filters.js:80 -#: templates/js/table_filters.js:146 +#: build/templates/build/detail.html:44 stock/forms.py:364 +msgid "Destination" +msgstr "" + +#: build/templates/build/detail.html:51 +msgid "Destination location not specified" +msgstr "" + +#: build/templates/build/detail.html:68 +#: stock/templates/stock/item_base.html:251 templates/js/stock.js:565 +#: templates/js/stock.js:968 templates/js/table_filters.js:80 +#: templates/js/table_filters.js:151 msgid "Batch" msgstr "" -#: build/templates/build/detail.html:61 +#: build/templates/build/detail.html:95 #: order/templates/order/order_base.html:98 -#: order/templates/order/sales_order_base.html:100 templates/js/build.js:85 +#: order/templates/order/sales_order_base.html:100 templates/js/build.js:683 msgid "Created" msgstr "" -#: build/templates/build/detail.html:67 -msgid "Enough Parts?" +#: build/templates/build/detail.html:105 +msgid "BOM Price" msgstr "" -#: build/templates/build/detail.html:70 -msgid "Yes" +#: build/templates/build/detail.html:110 +msgid "BOM pricing is incomplete" msgstr "" -#: build/templates/build/detail.html:72 -msgid "No" +#: build/templates/build/detail.html:113 +msgid "No pricing information" msgstr "" -#: build/templates/build/detail.html:80 templates/js/build.js:90 +#: build/templates/build/detail.html:120 templates/js/build.js:661 +#: templates/js/build.js:688 msgid "Completed" msgstr "" -#: build/templates/build/index.html:25 build/views.py:403 +#: build/templates/build/edit_build_item.html:7 +msgid "Alter the quantity of stock allocated to the build output" +msgstr "" + +#: build/templates/build/index.html:25 build/views.py:658 msgid "New Build Order" msgstr "" @@ -748,94 +856,163 @@ msgid "Details" msgstr "" #: build/templates/build/tabs.html:8 -msgid "Allocated Parts" +msgid "Allocate Parts" msgstr "" -#: build/templates/build/unallocate.html:8 +#: build/templates/build/unallocate.html:10 msgid "Are you sure you wish to unallocate all stock for this build?" msgstr "" -#: build/views.py:77 -msgid "Confirm build cancellation" +#: build/templates/build/unallocate.html:12 +msgid "All incomplete stock allocations will be removed from the build" msgstr "" -#: build/views.py:82 +#: build/views.py:79 msgid "Build was cancelled" msgstr "" -#: build/views.py:98 +#: build/views.py:93 msgid "Allocate Stock" msgstr "" -#: build/views.py:112 -msgid "No matching build found" +#: build/views.py:157 build/views.py:317 build/views.py:490 +msgid "Build output must be specified" msgstr "" -#: build/views.py:131 -msgid "Confirm stock allocation" +#: build/views.py:171 +msgid "Allocated stock to build output" msgstr "" -#: build/views.py:132 -msgid "Check the confirmation box at the bottom of the list" +#: build/views.py:183 +msgid "Create Build Output" msgstr "" -#: build/views.py:152 build/views.py:467 -msgid "Unallocate Stock" +#: build/views.py:207 stock/models.py:832 stock/views.py:1645 +msgid "Serial numbers already exist" msgstr "" -#: build/views.py:166 +#: build/views.py:216 +msgid "Serial numbers required for trackable build output" +msgstr "" + +#: build/views.py:282 +msgid "Delete Build Output" +msgstr "" + +#: build/views.py:302 build/views.py:387 msgid "Confirm unallocation of build stock" msgstr "" -#: build/views.py:167 stock/views.py:421 +#: build/views.py:303 build/views.py:388 stock/views.py:413 msgid "Check the confirmation box" msgstr "" -#: build/views.py:270 -msgid "Confirm completion of build" +#: build/views.py:315 +msgid "Build output does not match build" msgstr "" -#: build/views.py:277 -msgid "Invalid location selected" +#: build/views.py:329 +msgid "Build output deleted" msgstr "" -#: build/views.py:302 stock/views.py:1653 -#, python-brace-format -msgid "The following serial numbers already exist: ({sn})" +#: build/views.py:412 +msgid "Complete Build Order" msgstr "" -#: build/views.py:323 -msgid "Build marked as COMPLETE" +#: build/views.py:418 +msgid "Build order cannot be completed" msgstr "" -#: build/views.py:431 +#: build/views.py:429 +msgid "Completed build order" +msgstr "" + +#: build/views.py:445 +msgid "Complete Build Output" +msgstr "" + +#: build/views.py:481 +msgid "Quantity to complete cannot exceed build output quantity" +msgstr "" + +#: build/views.py:487 +msgid "Confirm completion of incomplete build" +msgstr "" + +#: build/views.py:578 +msgid "Build output completed" +msgstr "" + +#: build/views.py:703 msgid "Created new build" msgstr "" -#: build/views.py:441 +#: build/views.py:724 msgid "Edit Build Details" msgstr "" -#: build/views.py:447 +#: build/views.py:758 msgid "Edited build" msgstr "" -#: build/views.py:473 +#: build/views.py:784 msgid "Removed parts from build allocation" msgstr "" -#: build/views.py:483 -msgid "Allocate new Part" +#: build/views.py:796 +msgid "Allocate stock to build output" msgstr "" -#: build/views.py:637 +#: build/views.py:840 +msgid "Item must be currently in stock" +msgstr "" + +#: build/views.py:846 +msgid "Stock item is over-allocated" +msgstr "" + +#: build/views.py:847 templates/js/bom.js:215 templates/js/build.js:503 +#: templates/js/build.js:731 +msgid "Available" +msgstr "" + +#: build/views.py:849 +msgid "Stock item must be selected" +msgstr "" + +#: build/views.py:1011 msgid "Edit Stock Allocation" msgstr "" -#: build/views.py:642 +#: build/views.py:1016 msgid "Updated Build Item" msgstr "" +#: build/views.py:1045 +msgid "Add Build Order Attachment" +msgstr "" + +#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:96 +#: stock/views.py:176 +msgid "Added attachment" +msgstr "" + +#: build/views.py:1095 order/views.py:191 order/views.py:213 +msgid "Edit Attachment" +msgstr "" + +#: build/views.py:1106 order/views.py:196 order/views.py:218 +msgid "Attachment updated" +msgstr "" + +#: build/views.py:1116 order/views.py:233 order/views.py:248 +msgid "Delete Attachment" +msgstr "" + +#: build/views.py:1122 order/views.py:240 order/views.py:255 stock/views.py:234 +msgid "Deleted attachment" +msgstr "" + #: common/models.py:51 msgid "InvenTree Instance Name" msgstr "" @@ -1041,8 +1218,8 @@ msgstr "" msgid "Does this company manufacture parts?" msgstr "" -#: company/models.py:283 stock/models.py:335 -#: stock/templates/stock/item_base.html:164 +#: company/models.py:283 stock/models.py:337 +#: stock/templates/stock/item_base.html:177 msgid "Base Part" msgstr "" @@ -1083,7 +1260,7 @@ msgid "Part packaging" msgstr "" #: company/templates/company/assigned_stock.html:9 -#: company/templates/company/tabs.html:25 +#: company/templates/company/tabs.html:25 templates/js/build.js:395 msgid "Assigned Stock" msgstr "" @@ -1113,14 +1290,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:273 templates/js/company.js:48 +#: stock/templates/stock/item_base.html:286 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:370 -#: stock/models.py:371 stock/templates/stock/item_base.html:191 +#: order/templates/order/sales_order_base.html:81 stock/models.py:372 +#: stock/models.py:373 stock/templates/stock/item_base.html:204 #: templates/js/company.js:40 templates/js/order.js:236 msgid "Customer" msgstr "" @@ -1136,7 +1313,7 @@ msgstr "" #: company/templates/company/detail_part.html:18 #: order/templates/order/purchase_order_detail.html:68 -#: part/templates/part/supplier.html:14 templates/js/stock.js:798 +#: part/templates/part/supplier.html:14 templates/js/stock.js:845 msgid "New Supplier Part" msgstr "" @@ -1160,7 +1337,7 @@ msgid "Delete Parts" msgstr "" #: company/templates/company/detail_part.html:63 -#: part/templates/part/category.html:116 templates/js/stock.js:792 +#: part/templates/part/category.html:116 templates/js/stock.js:839 msgid "New Part" msgstr "" @@ -1192,7 +1369,7 @@ msgstr "" #: company/templates/company/detail_stock.html:35 #: company/templates/company/supplier_part_stock.html:33 -#: part/templates/part/bom.html:67 part/templates/part/category.html:112 +#: part/templates/part/bom.html:63 part/templates/part/category.html:112 #: part/templates/part/category.html:126 part/templates/part/stock.html:51 #: templates/stock_table.html:7 msgid "Export" @@ -1252,8 +1429,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:344 -#: stock/templates/stock/item_base.html:278 templates/js/company.js:180 +#: company/templates/company/supplier_part_base.html:19 stock/models.py:346 +#: stock/templates/stock/item_base.html:291 templates/js/company.js:180 msgid "Supplier Part" msgstr "" @@ -1310,7 +1487,7 @@ 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:2281 +#: part/templates/part/sale_prices.html:13 part/views.py:2292 msgid "Add Price Break" msgstr "" @@ -1320,7 +1497,7 @@ 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:248 +#: part/templates/part/sale_prices.html:85 templates/js/bom.js:234 msgid "Price" msgstr "" @@ -1345,8 +1522,8 @@ 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:153 -#: templates/js/part.js:411 templates/js/stock.js:453 templates/navbar.html:22 +#: 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" msgstr "" @@ -1429,7 +1606,7 @@ msgstr "" msgid "Edit Supplier Part" msgstr "" -#: company/views.py:278 templates/js/stock.js:799 +#: company/views.py:278 templates/js/stock.js:846 msgid "Create new Supplier Part" msgstr "" @@ -1437,15 +1614,15 @@ msgstr "" msgid "Delete Supplier Part" msgstr "" -#: company/views.py:416 part/views.py:2287 +#: company/views.py:416 part/views.py:2298 msgid "Added new price break" msgstr "" -#: company/views.py:453 part/views.py:2332 +#: company/views.py:453 part/views.py:2343 msgid "Edit Price Break" msgstr "" -#: company/views.py:469 part/views.py:2348 +#: company/views.py:469 part/views.py:2359 msgid "Delete Price Break" msgstr "" @@ -1518,7 +1695,7 @@ msgstr "" msgid "Order notes" msgstr "" -#: order/models.py:140 order/models.py:318 +#: order/models.py:140 order/models.py:326 msgid "Purchase order status" msgstr "" @@ -1538,8 +1715,8 @@ msgstr "" msgid "Date order was completed" msgstr "" -#: order/models.py:185 order/models.py:259 part/views.py:1398 -#: stock/models.py:241 stock/models.py:805 +#: order/models.py:185 order/models.py:267 part/views.py:1409 +#: stock/models.py:243 stock/models.py:816 msgid "Quantity must be greater than zero" msgstr "" @@ -1547,69 +1724,69 @@ msgstr "" msgid "Part supplier must match PO supplier" msgstr "" -#: order/models.py:254 +#: order/models.py:262 msgid "Lines can only be received against an order marked as 'Placed'" msgstr "" -#: order/models.py:314 +#: order/models.py:322 msgid "Company to which the items are being sold" msgstr "" -#: order/models.py:320 +#: order/models.py:328 msgid "Customer order reference code" msgstr "" -#: order/models.py:359 +#: order/models.py:367 msgid "SalesOrder cannot be shipped as it is not currently pending" msgstr "" -#: order/models.py:436 +#: order/models.py:454 msgid "Item quantity" msgstr "" -#: order/models.py:438 +#: order/models.py:456 msgid "Line item reference" msgstr "" -#: order/models.py:440 +#: order/models.py:458 msgid "Line item notes" msgstr "" -#: order/models.py:466 order/templates/order/order_base.html:9 +#: order/models.py:484 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 -#: stock/templates/stock/item_base.html:252 templates/js/order.js:139 +#: stock/templates/stock/item_base.html:265 templates/js/order.js:139 msgid "Purchase Order" msgstr "" -#: order/models.py:479 +#: order/models.py:497 msgid "Supplier part" msgstr "" -#: order/models.py:482 +#: order/models.py:500 msgid "Number of items received" msgstr "" -#: order/models.py:576 +#: order/models.py:594 msgid "Cannot allocate stock item to a line with a different part" msgstr "" -#: order/models.py:578 +#: order/models.py:596 msgid "Cannot allocate stock to a line without a part" msgstr "" -#: order/models.py:581 +#: order/models.py:599 msgid "Allocation quantity cannot exceed stock quantity" msgstr "" -#: order/models.py:591 +#: order/models.py:609 msgid "Quantity must be 1 for serialized stock item" msgstr "" -#: order/models.py:608 +#: order/models.py:626 msgid "Select stock item to allocate" msgstr "" -#: order/models.py:611 +#: order/models.py:629 msgid "Enter stock allocation quantity" msgstr "" @@ -1726,14 +1903,9 @@ msgstr "" msgid "Line Items" msgstr "" -#: order/templates/order/po_tabs.html:11 order/templates/order/so_tabs.html:16 -#: part/templates/part/tabs.html:67 stock/templates/stock/tabs.html:32 -msgid "Attachments" -msgstr "" - #: order/templates/order/purchase_order_detail.html:17 -#: order/templates/order/sales_order_detail.html:19 order/views.py:1117 -#: order/views.py:1232 +#: order/templates/order/sales_order_detail.html:19 order/views.py:1115 +#: order/views.py:1199 msgid "Add Line Item" msgstr "" @@ -1744,7 +1916,7 @@ msgstr "" #: order/templates/order/purchase_order_detail.html:39 #: order/templates/order/purchase_order_detail.html:119 #: part/templates/part/category.html:173 part/templates/part/category.html:215 -#: templates/js/stock.js:804 +#: templates/js/stock.js:851 msgid "New Location" msgstr "" @@ -1785,7 +1957,7 @@ msgid "Select parts to receive against this order" msgstr "" #: order/templates/order/receive_parts.html:21 -#: part/templates/part/part_base.html:145 templates/js/part.js:427 +#: part/templates/part/part_base.html:145 templates/js/part.js:434 msgid "On Order" msgstr "" @@ -1824,10 +1996,40 @@ msgstr "" 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 +#: stock/templates/stock/item_base.html:191 templates/js/build.js:402 +msgid "Serial Number" +msgstr "" + +#: order/templates/order/sales_order_detail.html:96 templates/js/build.js:443 +#: templates/js/build.js:742 +msgid "Edit stock allocation" +msgstr "" + +#: order/templates/order/sales_order_detail.html:97 templates/js/build.js:445 +#: templates/js/build.js:743 +msgid "Delete stock allocation" +msgstr "" + +#: order/templates/order/sales_order_detail.html:225 +#: part/templates/part/tabs.html:23 templates/js/build.js:507 +#: templates/js/build.js:738 +msgid "Allocated" +msgstr "" + #: order/templates/order/sales_order_detail.html:227 msgid "Fulfilled" msgstr "" +#: order/templates/order/sales_order_detail.html:275 +msgid "Buy parts" +msgstr "" + +#: order/templates/order/sales_order_detail.html:279 +msgid "Build parts" +msgstr "" + #: order/templates/order/sales_order_detail.html:282 msgid "Allocate parts" msgstr "" @@ -1878,143 +2080,131 @@ msgstr "" msgid "Add Purchase Order Attachment" msgstr "" -#: order/views.py:109 order/views.py:157 part/views.py:92 stock/views.py:175 -msgid "Added attachment" -msgstr "" - -#: order/views.py:148 +#: order/views.py:150 msgid "Add Sales Order Attachment" msgstr "" -#: order/views.py:184 order/views.py:206 -msgid "Edit Attachment" -msgstr "" - -#: order/views.py:189 order/views.py:211 -msgid "Attachment updated" -msgstr "" - -#: order/views.py:226 order/views.py:241 -msgid "Delete Attachment" -msgstr "" - -#: order/views.py:233 order/views.py:248 stock/views.py:233 -msgid "Deleted attachment" -msgstr "" - -#: order/views.py:301 +#: order/views.py:310 msgid "Create Purchase Order" msgstr "" -#: order/views.py:333 +#: order/views.py:345 msgid "Create Sales Order" msgstr "" -#: order/views.py:364 +#: order/views.py:380 msgid "Edit Purchase Order" msgstr "" -#: order/views.py:385 +#: order/views.py:401 msgid "Edit Sales Order" msgstr "" -#: order/views.py:402 +#: order/views.py:418 msgid "Cancel Order" msgstr "" -#: order/views.py:418 order/views.py:451 +#: order/views.py:428 order/views.py:455 msgid "Confirm order cancellation" msgstr "" -#: order/views.py:436 +#: order/views.py:431 order/views.py:458 +msgid "Order cannot be cancelled" +msgstr "" + +#: order/views.py:445 msgid "Cancel sales order" msgstr "" -#: order/views.py:457 -msgid "Could not cancel order" -msgstr "" - -#: order/views.py:471 +#: order/views.py:472 msgid "Issue Order" msgstr "" -#: order/views.py:487 +#: order/views.py:482 msgid "Confirm order placement" msgstr "" -#: order/views.py:508 +#: order/views.py:492 +msgid "Purchase order issued" +msgstr "" + +#: order/views.py:503 msgid "Complete Order" msgstr "" -#: order/views.py:544 +#: order/views.py:520 +msgid "Confirm order completion" +msgstr "" + +#: order/views.py:531 +msgid "Purchase order completed" +msgstr "" + +#: order/views.py:541 msgid "Ship Order" msgstr "" -#: order/views.py:561 +#: order/views.py:558 msgid "Confirm order shipment" msgstr "" -#: order/views.py:567 +#: order/views.py:564 msgid "Could not ship order" msgstr "" -#: order/views.py:619 +#: order/views.py:616 msgid "Receive Parts" msgstr "" -#: order/views.py:687 +#: order/views.py:684 msgid "Items received" msgstr "" -#: order/views.py:701 +#: order/views.py:698 msgid "No destination set" msgstr "" -#: order/views.py:746 +#: order/views.py:743 msgid "Error converting quantity to number" msgstr "" -#: order/views.py:752 +#: order/views.py:749 msgid "Receive quantity less than zero" msgstr "" -#: order/views.py:758 +#: order/views.py:755 msgid "No lines specified" msgstr "" -#: order/views.py:1138 -msgid "Invalid Purchase Order" +#: order/views.py:1125 +msgid "Supplier part must be specified" msgstr "" -#: order/views.py:1146 +#: order/views.py:1131 msgid "Supplier must match for Part and Order" msgstr "" -#: order/views.py:1151 -msgid "Invalid SupplierPart selection" -msgstr "" - -#: order/views.py:1284 order/views.py:1303 +#: order/views.py:1251 order/views.py:1270 msgid "Edit Line Item" msgstr "" -#: order/views.py:1320 order/views.py:1333 +#: order/views.py:1287 order/views.py:1300 msgid "Delete Line Item" msgstr "" -#: order/views.py:1326 order/views.py:1339 +#: order/views.py:1293 order/views.py:1306 msgid "Deleted line item" msgstr "" -#: order/views.py:1348 +#: order/views.py:1315 msgid "Allocate Stock to Order" msgstr "" -#: order/views.py:1418 +#: order/views.py:1385 msgid "Edit Allocation Quantity" msgstr "" -#: order/views.py:1434 +#: order/views.py:1401 msgid "Remove allocation" msgstr "" @@ -2088,7 +2278,7 @@ msgstr "" msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:93 part/models.py:1504 +#: part/forms.py:93 part/models.py:1582 msgid "Parent Part" msgstr "" @@ -2166,208 +2356,213 @@ msgstr "" msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" msgstr "" -#: part/models.py:435 +#: part/models.py:452 msgid "Next available serial numbers are" msgstr "" -#: part/models.py:439 +#: part/models.py:456 msgid "Next available serial number is" msgstr "" -#: part/models.py:444 +#: part/models.py:461 msgid "Most recent serial number is" msgstr "" -#: part/models.py:522 +#: part/models.py:539 msgid "Part must be unique for name, IPN and revision" msgstr "" -#: part/models.py:537 part/templates/part/detail.html:19 +#: part/models.py:568 part/templates/part/detail.html:19 msgid "Part name" msgstr "" -#: part/models.py:541 +#: part/models.py:572 msgid "Is this part a template part?" msgstr "" -#: part/models.py:550 +#: part/models.py:581 msgid "Is this part a variant of another part?" msgstr "" -#: part/models.py:552 +#: part/models.py:583 msgid "Part description" msgstr "" -#: part/models.py:554 +#: part/models.py:585 msgid "Part keywords to improve visibility in search results" msgstr "" -#: part/models.py:559 +#: part/models.py:590 msgid "Part category" msgstr "" -#: part/models.py:561 +#: part/models.py:592 msgid "Internal Part Number" msgstr "" -#: part/models.py:563 +#: part/models.py:594 msgid "Part revision or version number" msgstr "" -#: part/models.py:577 +#: part/models.py:608 msgid "Where is this item normally stored?" msgstr "" -#: part/models.py:621 +#: part/models.py:652 msgid "Default supplier part" msgstr "" -#: part/models.py:624 +#: part/models.py:655 msgid "Minimum allowed stock level" msgstr "" -#: part/models.py:626 +#: part/models.py:657 msgid "Stock keeping units for this part" msgstr "" -#: part/models.py:628 +#: part/models.py:659 msgid "Can this part be built from other parts?" msgstr "" -#: part/models.py:630 +#: part/models.py:661 msgid "Can this part be used to build other parts?" msgstr "" -#: part/models.py:632 +#: part/models.py:663 msgid "Does this part have tracking for unique items?" msgstr "" -#: part/models.py:634 +#: part/models.py:665 msgid "Can this part be purchased from external suppliers?" msgstr "" -#: part/models.py:636 +#: part/models.py:667 msgid "Can this part be sold to customers?" msgstr "" -#: part/models.py:638 +#: part/models.py:669 msgid "Is this part active?" msgstr "" -#: part/models.py:640 +#: part/models.py:671 msgid "Is this a virtual part, such as a software product or license?" msgstr "" -#: part/models.py:642 +#: part/models.py:673 msgid "Part notes - supports Markdown formatting" msgstr "" -#: part/models.py:644 +#: part/models.py:675 msgid "Stored BOM checksum" msgstr "" -#: part/models.py:1377 +#: part/models.py:1455 msgid "Test templates can only be created for trackable parts" msgstr "" -#: part/models.py:1394 +#: part/models.py:1472 msgid "Test with this name already exists for this part" msgstr "" -#: part/models.py:1413 templates/js/part.js:560 templates/js/stock.js:92 +#: part/models.py:1491 templates/js/part.js:567 templates/js/stock.js:92 msgid "Test Name" msgstr "" -#: part/models.py:1414 +#: part/models.py:1492 msgid "Enter a name for the test" msgstr "" -#: part/models.py:1419 +#: part/models.py:1497 msgid "Test Description" msgstr "" -#: part/models.py:1420 +#: part/models.py:1498 msgid "Enter description for this test" msgstr "" -#: part/models.py:1426 +#: part/models.py:1503 templates/js/part.js:576 +#: templates/js/table_filters.js:172 +msgid "Required" +msgstr "" + +#: part/models.py:1504 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1431 templates/js/part.js:577 +#: part/models.py:1509 templates/js/part.js:584 msgid "Requires Value" msgstr "" -#: part/models.py:1432 +#: part/models.py:1510 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1437 templates/js/part.js:584 +#: part/models.py:1515 templates/js/part.js:591 msgid "Requires Attachment" msgstr "" -#: part/models.py:1438 +#: part/models.py:1516 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1471 +#: part/models.py:1549 msgid "Parameter template name must be unique" msgstr "" -#: part/models.py:1476 +#: part/models.py:1554 msgid "Parameter Name" msgstr "" -#: part/models.py:1478 +#: part/models.py:1556 msgid "Parameter Units" msgstr "" -#: part/models.py:1506 +#: part/models.py:1584 msgid "Parameter Template" msgstr "" -#: part/models.py:1508 +#: part/models.py:1586 msgid "Parameter Value" msgstr "" -#: part/models.py:1545 +#: part/models.py:1623 msgid "Select parent part" msgstr "" -#: part/models.py:1553 +#: part/models.py:1631 msgid "Select part to be used in BOM" msgstr "" -#: part/models.py:1559 +#: part/models.py:1637 msgid "BOM quantity for this BOM item" msgstr "" -#: part/models.py:1561 +#: part/models.py:1639 msgid "This BOM item is optional" msgstr "" -#: part/models.py:1564 +#: part/models.py:1642 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "" -#: part/models.py:1567 +#: part/models.py:1645 msgid "BOM item reference" msgstr "" -#: part/models.py:1570 +#: part/models.py:1648 msgid "BOM item notes" msgstr "" -#: part/models.py:1572 +#: part/models.py:1650 msgid "BOM line checksum" msgstr "" -#: part/models.py:1636 part/views.py:1404 part/views.py:1456 -#: stock/models.py:231 +#: part/models.py:1717 part/views.py:1415 part/views.py:1467 +#: stock/models.py:233 msgid "Quantity must be integer value for trackable parts" msgstr "" -#: part/models.py:1645 +#: part/models.py:1733 msgid "BOM Item" msgstr "" @@ -2385,10 +2580,10 @@ msgstr "" #: part/templates/part/allocation.html:28 #: part/templates/part/allocation.html:45 #: stock/templates/stock/item_base.html:8 -#: stock/templates/stock/item_base.html:58 -#: stock/templates/stock/item_base.html:260 -#: stock/templates/stock/stock_adjust.html:16 templates/js/build.js:126 -#: templates/js/stock.js:661 templates/js/stock.js:897 +#: stock/templates/stock/item_base.html:72 +#: stock/templates/stock/item_base.html:273 +#: 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" msgstr "" @@ -2400,68 +2595,68 @@ msgstr "" msgid "Bill of Materials" msgstr "" -#: part/templates/part/bom.html:38 +#: part/templates/part/bom.html:34 msgid "Remove selected BOM items" msgstr "" -#: part/templates/part/bom.html:41 +#: part/templates/part/bom.html:37 msgid "Import BOM data" msgstr "" -#: part/templates/part/bom.html:42 +#: part/templates/part/bom.html:38 msgid "Import from File" msgstr "" -#: part/templates/part/bom.html:45 +#: part/templates/part/bom.html:41 msgid "Copy BOM from parent part" msgstr "" -#: part/templates/part/bom.html:46 +#: part/templates/part/bom.html:42 msgid "Copy from Parent" msgstr "" -#: part/templates/part/bom.html:49 +#: part/templates/part/bom.html:45 msgid "New BOM Item" msgstr "" -#: part/templates/part/bom.html:50 +#: part/templates/part/bom.html:46 msgid "Add Item" msgstr "" -#: part/templates/part/bom.html:52 +#: part/templates/part/bom.html:48 msgid "Finish Editing" msgstr "" -#: part/templates/part/bom.html:53 +#: part/templates/part/bom.html:49 msgid "Finished" msgstr "" -#: part/templates/part/bom.html:57 +#: part/templates/part/bom.html:53 msgid "Edit BOM" msgstr "" -#: part/templates/part/bom.html:58 part/templates/part/params.html:38 +#: part/templates/part/bom.html:54 part/templates/part/params.html:38 #: templates/InvenTree/settings/user.html:19 msgid "Edit" msgstr "" -#: part/templates/part/bom.html:61 +#: part/templates/part/bom.html:57 msgid "Validate Bill of Materials" msgstr "" -#: part/templates/part/bom.html:62 +#: part/templates/part/bom.html:58 msgid "Validate" msgstr "" -#: part/templates/part/bom.html:66 part/views.py:1695 +#: part/templates/part/bom.html:62 part/views.py:1706 msgid "Export Bill of Materials" msgstr "" -#: part/templates/part/bom.html:127 +#: part/templates/part/bom.html:123 msgid "Delete selected BOM items?" msgstr "" -#: part/templates/part/bom.html:128 +#: part/templates/part/bom.html:124 msgid "All selected BOM items will be deleted" msgstr "" @@ -2541,7 +2736,7 @@ msgstr "" msgid "Part Builds" msgstr "" -#: part/templates/part/build.html:14 +#: part/templates/part/build.html:15 msgid "Start New Build" msgstr "" @@ -2549,7 +2744,7 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:24 part/views.py:2098 +#: part/templates/part/category.html:24 part/views.py:2109 msgid "Create new part category" msgstr "" @@ -2621,7 +2816,7 @@ msgstr "" msgid "Create new Part Category" msgstr "" -#: part/templates/part/category.html:216 stock/views.py:1343 +#: part/templates/part/category.html:216 stock/views.py:1338 msgid "Create new Stock Location" msgstr "" @@ -2646,11 +2841,11 @@ msgid "Part Details" msgstr "" #: part/templates/part/detail.html:25 part/templates/part/part_base.html:95 -#: templates/js/part.js:141 +#: templates/js/part.js:180 msgid "IPN" msgstr "" -#: part/templates/part/detail.html:32 templates/js/part.js:145 +#: part/templates/part/detail.html:32 templates/js/part.js:184 msgid "Revision" msgstr "" @@ -2667,7 +2862,7 @@ msgid "Variant Of" msgstr "" #: part/templates/part/detail.html:70 part/templates/part/set_category.html:15 -#: templates/js/part.js:398 +#: templates/js/part.js:405 msgid "Category" msgstr "" @@ -2708,7 +2903,7 @@ msgid "Part is not a virtual part" msgstr "" #: part/templates/part/detail.html:148 stock/forms.py:248 -#: templates/js/table_filters.js:23 templates/js/table_filters.js:243 +#: templates/js/table_filters.js:23 templates/js/table_filters.js:248 msgid "Template" msgstr "" @@ -2720,7 +2915,7 @@ msgstr "" msgid "Part is not a template part" msgstr "" -#: part/templates/part/detail.html:158 templates/js/table_filters.js:255 +#: part/templates/part/detail.html:158 templates/js/table_filters.js:260 msgid "Assembly" msgstr "" @@ -2732,7 +2927,7 @@ msgstr "" msgid "Part cannot be assembled from other parts" msgstr "" -#: part/templates/part/detail.html:168 templates/js/table_filters.js:259 +#: part/templates/part/detail.html:168 templates/js/table_filters.js:264 msgid "Component" msgstr "" @@ -2745,7 +2940,7 @@ msgid "Part cannot be used in assemblies" msgstr "" #: part/templates/part/detail.html:178 templates/js/table_filters.js:31 -#: templates/js/table_filters.js:271 +#: templates/js/table_filters.js:276 msgid "Trackable" msgstr "" @@ -2765,7 +2960,7 @@ msgstr "" msgid "Part can be purchased from external suppliers" msgstr "" -#: part/templates/part/detail.html:198 templates/js/table_filters.js:267 +#: part/templates/part/detail.html:198 templates/js/table_filters.js:272 msgid "Salable" msgstr "" @@ -2778,7 +2973,8 @@ msgid "Part cannot be sold to customers" msgstr "" #: part/templates/part/detail.html:214 templates/js/table_filters.js:19 -#: templates/js/table_filters.js:55 templates/js/table_filters.js:238 +#: templates/js/table_filters.js:55 templates/js/table_filters.js:186 +#: templates/js/table_filters.js:243 msgid "Active" msgstr "" @@ -2806,13 +3002,13 @@ msgstr "" msgid "New Parameter" msgstr "" -#: part/templates/part/params.html:25 stock/models.py:1391 +#: part/templates/part/params.html:25 stock/models.py:1415 #: templates/js/stock.js:112 msgid "Value" msgstr "" #: part/templates/part/params.html:41 part/templates/part/supplier.html:19 -#: users/models.py:145 +#: users/models.py:146 msgid "Delete" msgstr "" @@ -2832,9 +3028,8 @@ msgstr "" msgid "This part is a variant of" msgstr "" -#: part/templates/part/part_base.html:36 templates/js/bom.js:175 -#: templates/js/company.js:155 templates/js/part.js:133 -#: templates/js/part.js:375 +#: part/templates/part/part_base.html:36 templates/js/company.js:155 +#: templates/js/part.js:95 templates/js/part.js:172 msgid "Inactive" msgstr "" @@ -2843,19 +3038,19 @@ msgid "Star this part" msgstr "" #: part/templates/part/part_base.html:49 -#: stock/templates/stock/item_base.html:88 +#: stock/templates/stock/item_base.html:101 #: stock/templates/stock/location.html:29 msgid "Barcode actions" msgstr "" #: part/templates/part/part_base.html:51 -#: stock/templates/stock/item_base.html:90 +#: stock/templates/stock/item_base.html:103 #: stock/templates/stock/location.html:31 msgid "Show QR Code" msgstr "" #: part/templates/part/part_base.html:52 -#: stock/templates/stock/item_base.html:91 +#: stock/templates/stock/item_base.html:104 #: stock/templates/stock/location.html:32 msgid "Print Label" msgstr "" @@ -2944,8 +3139,8 @@ msgstr "" msgid "Part Stock" msgstr "" -#: part/templates/part/stock_count.html:7 templates/js/bom.js:238 -#: templates/js/part.js:435 +#: part/templates/part/stock_count.html:7 templates/js/bom.js:224 +#: templates/js/part.js:442 msgid "No Stock" msgstr "" @@ -2985,7 +3180,7 @@ msgstr "" msgid "Used In" msgstr "" -#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:304 +#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:317 msgid "Tests" msgstr "" @@ -3013,184 +3208,196 @@ msgstr "" msgid "Add part attachment" msgstr "" -#: part/views.py:131 templates/attachment_table.html:34 +#: part/views.py:135 templates/attachment_table.html:34 msgid "Edit attachment" msgstr "" -#: part/views.py:137 +#: part/views.py:141 msgid "Part attachment updated" msgstr "" -#: part/views.py:152 +#: part/views.py:156 msgid "Delete Part Attachment" msgstr "" -#: part/views.py:160 +#: part/views.py:164 msgid "Deleted part attachment" msgstr "" -#: part/views.py:169 +#: part/views.py:173 msgid "Create Test Template" msgstr "" -#: part/views.py:198 +#: part/views.py:202 msgid "Edit Test Template" msgstr "" -#: part/views.py:214 +#: part/views.py:218 msgid "Delete Test Template" msgstr "" -#: part/views.py:223 +#: part/views.py:227 msgid "Set Part Category" msgstr "" -#: part/views.py:273 +#: part/views.py:277 #, python-brace-format msgid "Set category for {n} parts" msgstr "" -#: part/views.py:308 +#: part/views.py:312 msgid "Create Variant" msgstr "" -#: part/views.py:388 +#: part/views.py:394 msgid "Duplicate Part" msgstr "" -#: part/views.py:395 +#: part/views.py:401 msgid "Copied part" msgstr "" -#: part/views.py:514 templates/js/stock.js:793 +#: part/views.py:455 part/views.py:585 +msgid "Possible matches exist - confirm creation of new part" +msgstr "" + +#: part/views.py:520 templates/js/stock.js:840 msgid "Create New Part" msgstr "" -#: part/views.py:521 +#: part/views.py:527 msgid "Created new part" msgstr "" -#: part/views.py:736 +#: part/views.py:743 msgid "Part QR Code" msgstr "" -#: part/views.py:755 +#: part/views.py:762 msgid "Upload Part Image" msgstr "" -#: part/views.py:763 part/views.py:800 +#: part/views.py:770 part/views.py:807 msgid "Updated part image" msgstr "" -#: part/views.py:772 +#: part/views.py:779 msgid "Select Part Image" msgstr "" -#: part/views.py:803 +#: part/views.py:810 msgid "Part image not found" msgstr "" -#: part/views.py:814 +#: part/views.py:821 msgid "Edit Part Properties" msgstr "" -#: part/views.py:841 +#: part/views.py:848 msgid "Duplicate BOM" msgstr "" -#: part/views.py:872 +#: part/views.py:879 msgid "Confirm duplication of BOM from parent" msgstr "" -#: part/views.py:890 +#: part/views.py:900 msgid "Validate BOM" msgstr "" -#: part/views.py:1057 +#: part/views.py:923 +msgid "Confirm that the BOM is valid" +msgstr "" + +#: part/views.py:934 +msgid "Validated Bill of Materials" +msgstr "" + +#: part/views.py:1068 msgid "No BOM file provided" msgstr "" -#: part/views.py:1407 +#: part/views.py:1418 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1432 part/views.py:1435 +#: part/views.py:1443 part/views.py:1446 msgid "Select valid part" msgstr "" -#: part/views.py:1441 +#: part/views.py:1452 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1479 +#: part/views.py:1490 msgid "Select a part" msgstr "" -#: part/views.py:1485 +#: part/views.py:1496 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1489 +#: part/views.py:1500 msgid "Specify quantity" msgstr "" -#: part/views.py:1745 +#: part/views.py:1756 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1754 +#: part/views.py:1765 msgid "Part was deleted" msgstr "" -#: part/views.py:1763 +#: part/views.py:1774 msgid "Part Pricing" msgstr "" -#: part/views.py:1889 +#: part/views.py:1900 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1899 +#: part/views.py:1910 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1908 +#: part/views.py:1919 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1918 +#: part/views.py:1929 msgid "Create Part Parameter" msgstr "" -#: part/views.py:1970 +#: part/views.py:1981 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:1986 +#: part/views.py:1997 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:2045 +#: part/views.py:2056 msgid "Edit Part Category" msgstr "" -#: part/views.py:2082 +#: part/views.py:2093 msgid "Delete Part Category" msgstr "" -#: part/views.py:2090 +#: part/views.py:2101 msgid "Part category was deleted" msgstr "" -#: part/views.py:2153 +#: part/views.py:2164 msgid "Create BOM Item" msgstr "" -#: part/views.py:2221 +#: part/views.py:2232 msgid "Edit BOM item" msgstr "" -#: part/views.py:2271 +#: part/views.py:2282 msgid "Confim BOM item deletion" msgstr "" @@ -3222,6 +3429,10 @@ msgstr "" msgid "Asset file description" msgstr "" +#: stock/forms.py:111 +msgid "Enter unique serial numbers (or leave blank)" +msgstr "" + #: stock/forms.py:191 msgid "Label" msgstr "" @@ -3262,10 +3473,6 @@ msgstr "" msgid "Confirm removal of installed stock items" msgstr "" -#: stock/forms.py:364 -msgid "Destination" -msgstr "" - #: stock/forms.py:364 msgid "Destination stock location" msgstr "" @@ -3274,7 +3481,7 @@ msgstr "" msgid "Add note (required)" msgstr "" -#: stock/forms.py:370 stock/views.py:921 stock/views.py:1119 +#: stock/forms.py:370 stock/views.py:916 stock/views.py:1114 msgid "Confirm stock adjustment" msgstr "" @@ -3290,227 +3497,227 @@ msgstr "" msgid "Set the destination as the default location for selected parts" msgstr "" -#: stock/models.py:212 +#: stock/models.py:178 +msgid "Created stock item" +msgstr "" + +#: stock/models.py:214 msgid "StockItem with this serial number already exists" msgstr "" -#: stock/models.py:248 +#: stock/models.py:250 #, python-brace-format msgid "Part type ('{pf}') must be {pe}" msgstr "" -#: stock/models.py:258 stock/models.py:267 +#: stock/models.py:260 stock/models.py:269 msgid "Quantity must be 1 for item with a serial number" msgstr "" -#: stock/models.py:259 +#: stock/models.py:261 msgid "Serial number cannot be set if quantity greater than 1" msgstr "" -#: stock/models.py:281 +#: stock/models.py:283 msgid "Item cannot belong to itself" msgstr "" -#: stock/models.py:287 +#: stock/models.py:289 msgid "Item must have a build reference if is_building=True" msgstr "" -#: stock/models.py:294 +#: stock/models.py:296 msgid "Build reference does not point to the same part object" msgstr "" -#: stock/models.py:327 +#: stock/models.py:329 msgid "Parent Stock Item" msgstr "" -#: stock/models.py:336 +#: stock/models.py:338 msgid "Base part" msgstr "" -#: stock/models.py:345 +#: stock/models.py:347 msgid "Select a matching supplier part for this stock item" msgstr "" -#: stock/models.py:350 stock/templates/stock/stock_app_base.html:7 +#: stock/models.py:352 stock/templates/stock/stock_app_base.html:7 msgid "Stock Location" msgstr "" -#: stock/models.py:353 +#: stock/models.py:355 msgid "Where is this stock item located?" msgstr "" -#: stock/models.py:358 stock/templates/stock/item_base.html:199 +#: stock/models.py:360 stock/templates/stock/item_base.html:212 msgid "Installed In" msgstr "" -#: stock/models.py:361 +#: stock/models.py:363 msgid "Is this item installed in another item?" msgstr "" -#: stock/models.py:377 +#: stock/models.py:379 msgid "Serial number for this item" msgstr "" -#: stock/models.py:389 +#: stock/models.py:391 msgid "Batch code for this stock item" msgstr "" -#: stock/models.py:393 +#: stock/models.py:395 msgid "Stock Quantity" msgstr "" -#: stock/models.py:402 +#: stock/models.py:404 msgid "Source Build" msgstr "" -#: stock/models.py:404 +#: stock/models.py:406 msgid "Build for this stock item" msgstr "" -#: stock/models.py:415 +#: stock/models.py:417 msgid "Source Purchase Order" msgstr "" -#: stock/models.py:418 +#: stock/models.py:420 msgid "Purchase order for this stock item" msgstr "" -#: stock/models.py:424 +#: stock/models.py:426 msgid "Destination Sales Order" msgstr "" -#: stock/models.py:431 +#: stock/models.py:433 msgid "Destination Build Order" msgstr "" -#: stock/models.py:444 +#: stock/models.py:446 msgid "Delete this Stock Item when stock is depleted" msgstr "" -#: stock/models.py:454 stock/templates/stock/item_notes.html:14 +#: stock/models.py:456 stock/templates/stock/item_notes.html:14 #: stock/templates/stock/item_notes.html:30 msgid "Stock Item Notes" msgstr "" -#: stock/models.py:505 +#: stock/models.py:507 msgid "Assigned to Customer" msgstr "" -#: stock/models.py:507 +#: stock/models.py:509 msgid "Manually assigned to customer" msgstr "" -#: stock/models.py:520 +#: stock/models.py:522 msgid "Returned from customer" msgstr "" -#: stock/models.py:522 +#: stock/models.py:524 msgid "Returned to location" msgstr "" -#: stock/models.py:650 +#: stock/models.py:652 msgid "Installed into stock item" msgstr "" -#: stock/models.py:658 +#: stock/models.py:660 msgid "Installed stock item" msgstr "" -#: stock/models.py:682 +#: stock/models.py:684 msgid "Uninstalled stock item" msgstr "" -#: stock/models.py:701 +#: stock/models.py:703 msgid "Uninstalled into location" msgstr "" -#: stock/models.py:796 +#: stock/models.py:807 msgid "Part is not set as trackable" msgstr "" -#: stock/models.py:802 +#: stock/models.py:813 msgid "Quantity must be integer" msgstr "" -#: stock/models.py:808 +#: stock/models.py:819 #, python-brace-format msgid "Quantity must not exceed available stock quantity ({n})" msgstr "" -#: stock/models.py:811 +#: stock/models.py:822 msgid "Serial numbers must be a list of integers" msgstr "" -#: stock/models.py:814 +#: stock/models.py:825 msgid "Quantity does not match serial numbers" msgstr "" -#: stock/models.py:824 -msgid "Serial numbers already exist: " -msgstr "" - -#: stock/models.py:849 +#: stock/models.py:857 msgid "Add serial number" msgstr "" -#: stock/models.py:852 +#: stock/models.py:860 #, python-brace-format msgid "Serialized {n} items" msgstr "" -#: stock/models.py:963 +#: stock/models.py:971 msgid "StockItem cannot be moved as it is not in stock" msgstr "" -#: stock/models.py:1292 +#: stock/models.py:1316 msgid "Tracking entry title" msgstr "" -#: stock/models.py:1294 +#: stock/models.py:1318 msgid "Entry notes" msgstr "" -#: stock/models.py:1296 +#: stock/models.py:1320 msgid "Link to external page for further information" msgstr "" -#: stock/models.py:1356 +#: stock/models.py:1380 msgid "Value must be provided for this test" msgstr "" -#: stock/models.py:1362 +#: stock/models.py:1386 msgid "Attachment must be uploaded for this test" msgstr "" -#: stock/models.py:1379 +#: stock/models.py:1403 msgid "Test" msgstr "" -#: stock/models.py:1380 +#: stock/models.py:1404 msgid "Test name" msgstr "" -#: stock/models.py:1385 +#: stock/models.py:1409 msgid "Result" msgstr "" -#: stock/models.py:1386 templates/js/table_filters.js:157 +#: stock/models.py:1410 templates/js/table_filters.js:162 msgid "Test result" msgstr "" -#: stock/models.py:1392 +#: stock/models.py:1416 msgid "Test output value" msgstr "" -#: stock/models.py:1398 +#: stock/models.py:1422 msgid "Attachment" msgstr "" -#: stock/models.py:1399 +#: stock/models.py:1423 msgid "Test result attachment" msgstr "" -#: stock/models.py:1405 +#: stock/models.py:1429 msgid "Test notes" msgstr "" @@ -3527,132 +3734,145 @@ msgid "Stock Item Attachments" msgstr "" #: stock/templates/stock/item_base.html:20 +msgid "This stock item is in production and cannot be edited." +msgstr "" + +#: stock/templates/stock/item_base.html:21 +msgid "Edit the stock item from the build view." +msgstr "" + +#: stock/templates/stock/item_base.html:34 msgid "This stock item has not passed all required tests" msgstr "" -#: stock/templates/stock/item_base.html:26 +#: stock/templates/stock/item_base.html:40 msgid "This stock item is allocated to Sales Order" msgstr "" -#: stock/templates/stock/item_base.html:32 +#: stock/templates/stock/item_base.html:46 msgid "This stock item is allocated to Build" msgstr "" -#: stock/templates/stock/item_base.html:38 +#: stock/templates/stock/item_base.html:52 msgid "" "This stock item is serialized - it has a unique serial number and the " "quantity cannot be adjusted." msgstr "" -#: stock/templates/stock/item_base.html:42 +#: stock/templates/stock/item_base.html:56 msgid "This stock item cannot be deleted as it has child items" msgstr "" -#: stock/templates/stock/item_base.html:46 +#: stock/templates/stock/item_base.html:60 msgid "" "This stock item will be automatically deleted when all stock is depleted." msgstr "" -#: stock/templates/stock/item_base.html:94 templates/js/barcode.js:283 +#: stock/templates/stock/item_base.html:107 templates/js/barcode.js:283 #: templates/js/barcode.js:288 msgid "Unlink Barcode" msgstr "" -#: stock/templates/stock/item_base.html:96 +#: stock/templates/stock/item_base.html:109 msgid "Link Barcode" msgstr "" -#: stock/templates/stock/item_base.html:104 +#: stock/templates/stock/item_base.html:117 msgid "Stock adjustment actions" msgstr "" -#: stock/templates/stock/item_base.html:108 +#: stock/templates/stock/item_base.html:121 #: stock/templates/stock/location.html:41 templates/stock_table.html:23 msgid "Count stock" msgstr "" -#: stock/templates/stock/item_base.html:109 templates/stock_table.html:21 +#: stock/templates/stock/item_base.html:122 templates/stock_table.html:21 msgid "Add stock" msgstr "" -#: stock/templates/stock/item_base.html:110 templates/stock_table.html:22 +#: stock/templates/stock/item_base.html:123 templates/stock_table.html:22 msgid "Remove stock" msgstr "" -#: stock/templates/stock/item_base.html:112 +#: stock/templates/stock/item_base.html:125 msgid "Transfer stock" msgstr "" -#: stock/templates/stock/item_base.html:114 +#: stock/templates/stock/item_base.html:127 msgid "Serialize stock" msgstr "" -#: stock/templates/stock/item_base.html:118 +#: stock/templates/stock/item_base.html:131 msgid "Assign to customer" msgstr "" -#: stock/templates/stock/item_base.html:121 +#: stock/templates/stock/item_base.html:134 msgid "Return to stock" msgstr "" -#: stock/templates/stock/item_base.html:125 templates/js/stock.js:934 +#: stock/templates/stock/item_base.html:138 templates/js/stock.js:981 msgid "Uninstall stock item" msgstr "" -#: stock/templates/stock/item_base.html:125 +#: stock/templates/stock/item_base.html:138 msgid "Uninstall" msgstr "" -#: stock/templates/stock/item_base.html:134 +#: stock/templates/stock/item_base.html:147 #: stock/templates/stock/location.html:38 msgid "Stock actions" msgstr "" -#: stock/templates/stock/item_base.html:137 +#: stock/templates/stock/item_base.html:150 msgid "Convert to variant" msgstr "" -#: stock/templates/stock/item_base.html:140 +#: stock/templates/stock/item_base.html:153 msgid "Duplicate stock item" msgstr "" -#: stock/templates/stock/item_base.html:142 +#: stock/templates/stock/item_base.html:155 msgid "Edit stock item" msgstr "" -#: stock/templates/stock/item_base.html:145 +#: stock/templates/stock/item_base.html:158 msgid "Delete stock item" msgstr "" -#: stock/templates/stock/item_base.html:151 +#: stock/templates/stock/item_base.html:164 msgid "Generate test report" msgstr "" -#: stock/templates/stock/item_base.html:159 +#: stock/templates/stock/item_base.html:172 msgid "Stock Item Details" msgstr "" -#: stock/templates/stock/item_base.html:224 +#: stock/templates/stock/item_base.html:237 templates/js/build.js:426 msgid "No location set" msgstr "" -#: stock/templates/stock/item_base.html:231 +#: stock/templates/stock/item_base.html:244 msgid "Barcode Identifier" msgstr "" -#: stock/templates/stock/item_base.html:259 +#: stock/templates/stock/item_base.html:258 templates/js/build.js:626 +#: templates/navbar.html:25 +msgid "Build" +msgstr "" + +#: stock/templates/stock/item_base.html:272 msgid "Parent Item" msgstr "" -#: stock/templates/stock/item_base.html:284 +#: stock/templates/stock/item_base.html:297 msgid "Last Updated" msgstr "" -#: stock/templates/stock/item_base.html:289 +#: stock/templates/stock/item_base.html:302 msgid "Last Stocktake" msgstr "" -#: stock/templates/stock/item_base.html:293 +#: stock/templates/stock/item_base.html:306 msgid "No stocktake performed" msgstr "" @@ -3772,7 +3992,7 @@ msgstr "" msgid "The following stock items will be uninstalled" msgstr "" -#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1315 +#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1310 msgid "Convert Stock Item" msgstr "" @@ -3816,202 +4036,210 @@ msgstr "" msgid "Add Stock Item Attachment" msgstr "" -#: stock/views.py:209 +#: stock/views.py:210 msgid "Edit Stock Item Attachment" msgstr "" -#: stock/views.py:226 +#: stock/views.py:227 msgid "Delete Stock Item Attachment" msgstr "" -#: stock/views.py:243 +#: stock/views.py:244 msgid "Assign to Customer" msgstr "" -#: stock/views.py:281 +#: stock/views.py:254 +msgid "Customer must be specified" +msgstr "" + +#: stock/views.py:278 msgid "Return to Stock" msgstr "" -#: stock/views.py:301 +#: stock/views.py:288 msgid "Specify a valid location" msgstr "" -#: stock/views.py:305 +#: stock/views.py:299 msgid "Stock item returned from customer" msgstr "" -#: stock/views.py:317 +#: stock/views.py:309 msgid "Select Label Template" msgstr "" -#: stock/views.py:340 +#: stock/views.py:332 msgid "Select valid label" msgstr "" -#: stock/views.py:404 +#: stock/views.py:396 msgid "Delete All Test Data" msgstr "" -#: stock/views.py:420 +#: stock/views.py:412 msgid "Confirm test data deletion" msgstr "" -#: stock/views.py:440 +#: stock/views.py:432 msgid "Add Test Result" msgstr "" -#: stock/views.py:478 +#: stock/views.py:473 msgid "Edit Test Result" msgstr "" -#: stock/views.py:496 +#: stock/views.py:491 msgid "Delete Test Result" msgstr "" -#: stock/views.py:508 +#: stock/views.py:503 msgid "Select Test Report Template" msgstr "" -#: stock/views.py:523 +#: stock/views.py:518 msgid "Select valid template" msgstr "" -#: stock/views.py:576 +#: stock/views.py:571 msgid "Stock Export Options" msgstr "" -#: stock/views.py:698 +#: stock/views.py:693 msgid "Stock Item QR Code" msgstr "" -#: stock/views.py:724 +#: stock/views.py:719 msgid "Install Stock Item" msgstr "" -#: stock/views.py:824 +#: stock/views.py:819 msgid "Uninstall Stock Items" msgstr "" -#: stock/views.py:932 +#: stock/views.py:927 msgid "Uninstalled stock items" msgstr "" -#: stock/views.py:957 +#: stock/views.py:952 msgid "Adjust Stock" msgstr "" -#: stock/views.py:1067 +#: stock/views.py:1062 msgid "Move Stock Items" msgstr "" -#: stock/views.py:1068 +#: stock/views.py:1063 msgid "Count Stock Items" msgstr "" -#: stock/views.py:1069 +#: stock/views.py:1064 msgid "Remove From Stock" msgstr "" -#: stock/views.py:1070 +#: stock/views.py:1065 msgid "Add Stock Items" msgstr "" -#: stock/views.py:1071 +#: stock/views.py:1066 msgid "Delete Stock Items" msgstr "" -#: stock/views.py:1099 +#: stock/views.py:1094 msgid "Must enter integer value" msgstr "" -#: stock/views.py:1104 +#: stock/views.py:1099 msgid "Quantity must be positive" msgstr "" -#: stock/views.py:1111 +#: stock/views.py:1106 #, python-brace-format msgid "Quantity must not exceed {x}" msgstr "" -#: stock/views.py:1190 +#: stock/views.py:1185 #, python-brace-format msgid "Added stock to {n} items" msgstr "" -#: stock/views.py:1205 +#: stock/views.py:1200 #, python-brace-format msgid "Removed stock from {n} items" msgstr "" -#: stock/views.py:1218 +#: stock/views.py:1213 #, python-brace-format msgid "Counted stock for {n} items" msgstr "" -#: stock/views.py:1246 +#: stock/views.py:1241 msgid "No items were moved" msgstr "" -#: stock/views.py:1249 +#: stock/views.py:1244 #, python-brace-format msgid "Moved {n} items to {dest}" msgstr "" -#: stock/views.py:1268 +#: stock/views.py:1263 #, python-brace-format msgid "Deleted {n} stock items" msgstr "" -#: stock/views.py:1280 +#: stock/views.py:1275 msgid "Edit Stock Item" msgstr "" -#: stock/views.py:1365 +#: stock/views.py:1360 msgid "Serialize Stock" msgstr "" -#: stock/views.py:1559 +#: stock/views.py:1454 templates/js/build.js:210 +msgid "Create new Stock Item" +msgstr "" + +#: stock/views.py:1553 msgid "Duplicate Stock Item" msgstr "" -#: stock/views.py:1625 +#: stock/views.py:1619 msgid "Invalid quantity" msgstr "" -#: stock/views.py:1628 +#: stock/views.py:1622 msgid "Quantity cannot be less than zero" msgstr "" -#: stock/views.py:1632 +#: stock/views.py:1626 msgid "Invalid part selection" msgstr "" -#: stock/views.py:1681 +#: stock/views.py:1674 #, python-brace-format msgid "Created {n} new stock items" msgstr "" -#: stock/views.py:1700 stock/views.py:1716 +#: stock/views.py:1693 stock/views.py:1709 msgid "Created new stock item" msgstr "" -#: stock/views.py:1735 +#: stock/views.py:1728 msgid "Delete Stock Location" msgstr "" -#: stock/views.py:1749 +#: stock/views.py:1742 msgid "Delete Stock Item" msgstr "" -#: stock/views.py:1761 +#: stock/views.py:1754 msgid "Delete Stock Tracking Entry" msgstr "" -#: stock/views.py:1780 +#: stock/views.py:1773 msgid "Edit Stock Tracking Entry" msgstr "" -#: stock/views.py:1790 +#: stock/views.py:1783 msgid "Add Stock Tracking Entry" msgstr "" @@ -4067,11 +4295,11 @@ msgstr "" msgid "Enter a search query" msgstr "" -#: templates/InvenTree/search.html:191 templates/js/stock.js:528 +#: templates/InvenTree/search.html:191 templates/js/stock.js:289 msgid "Shipped to customer" msgstr "" -#: templates/InvenTree/search.html:194 templates/js/stock.js:538 +#: templates/InvenTree/search.html:194 templates/js/stock.js:299 msgid "No stock location set" msgstr "" @@ -4191,18 +4419,10 @@ msgstr "" msgid "User Information" msgstr "" -#: templates/InvenTree/settings/user.html:18 -msgid "Edit User Information" -msgstr "" - #: templates/InvenTree/settings/user.html:21 msgid "Change Password" msgstr "" -#: templates/InvenTree/settings/user.html:22 -msgid "Set Password" -msgstr "" - #: templates/InvenTree/settings/user.html:28 msgid "Username" msgstr "" @@ -4360,72 +4580,99 @@ msgstr "" msgid "Barcode does not match Stock Item" msgstr "" -#: templates/js/bom.js:156 templates/js/part.js:117 templates/js/part.js:344 -msgid "Trackable part" -msgstr "" - -#: templates/js/bom.js:160 templates/js/part.js:121 templates/js/part.js:348 -msgid "Virtual part" -msgstr "" - -#: templates/js/bom.js:164 templates/js/company.js:147 templates/js/part.js:125 -#: templates/js/part.js:353 -msgid "Template part" -msgstr "" - -#: templates/js/bom.js:169 +#: templates/js/bom.js:159 msgid "Open subassembly" msgstr "" -#: templates/js/bom.js:214 +#: templates/js/bom.js:200 msgid "Optional" msgstr "" -#: templates/js/bom.js:229 templates/js/build.js:133 -msgid "Available" -msgstr "" - -#: templates/js/bom.js:254 +#: templates/js/bom.js:240 msgid "No pricing available" msgstr "" -#: templates/js/bom.js:273 +#: templates/js/bom.js:259 templates/js/build.js:555 msgid "Actions" msgstr "" -#: templates/js/bom.js:281 +#: templates/js/bom.js:267 msgid "Validate BOM Item" msgstr "" -#: templates/js/bom.js:283 +#: templates/js/bom.js:269 msgid "This line has been validated" msgstr "" -#: templates/js/bom.js:285 +#: templates/js/bom.js:271 msgid "Edit BOM Item" msgstr "" -#: templates/js/bom.js:287 +#: templates/js/bom.js:273 msgid "Delete BOM Item" msgstr "" -#: templates/js/bom.js:507 +#: templates/js/bom.js:346 templates/js/build.js:289 +msgid "No BOM items found" +msgstr "" + +#: templates/js/bom.js:491 msgid "INACTIVE" msgstr "" -#: templates/js/bom.js:521 +#: templates/js/bom.js:505 msgid "Uses" msgstr "" -#: templates/js/bom.js:532 +#: templates/js/bom.js:516 msgid "No matching parts found" msgstr "" -#: templates/js/build.js:24 +#: templates/js/build.js:56 +msgid "Auto-allocate stock items to this output" +msgstr "" + +#: templates/js/build.js:62 +msgid "Complete build output" +msgstr "" + +#: templates/js/build.js:71 +msgid "Unallocate stock from build output" +msgstr "" + +#: templates/js/build.js:77 +msgid "Delete build output" +msgstr "" + +#: templates/js/build.js:209 templates/stock_table.html:13 +msgid "New Stock Item" +msgstr "" + +#: templates/js/build.js:477 +msgid "Required Part" +msgstr "" + +#: templates/js/build.js:498 +msgid "Quantity Per" +msgstr "" + +#: templates/js/build.js:562 +msgid "Build stock" +msgstr "" + +#: templates/js/build.js:566 templates/stock_table.html:25 +msgid "Order stock" +msgstr "" + +#: templates/js/build.js:569 +msgid "Allocate stock" +msgstr "" + +#: templates/js/build.js:610 msgid "No builds matching query" msgstr "" -#: templates/js/build.js:122 +#: templates/js/build.js:720 msgid "No parts allocated for" msgstr "" @@ -4445,8 +4692,11 @@ msgstr "" msgid "No supplier parts found" msgstr "" -#: templates/js/company.js:151 templates/js/part.js:129 -#: templates/js/part.js:357 +#: templates/js/company.js:147 templates/js/part.js:79 templates/js/part.js:164 +msgid "Template part" +msgstr "" + +#: templates/js/company.js:151 templates/js/part.js:83 templates/js/part.js:168 msgid "Assembled part" msgstr "" @@ -4458,7 +4708,7 @@ msgstr "" msgid "No purchase orders found" msgstr "" -#: templates/js/order.js:180 templates/js/stock.js:643 +#: templates/js/order.js:180 templates/js/stock.js:677 msgid "Date" msgstr "" @@ -4470,59 +4720,68 @@ msgstr "" msgid "Shipment Date" msgstr "" -#: templates/js/part.js:166 -msgid "No variants found" +#: templates/js/part.js:71 templates/js/part.js:156 +msgid "Trackable part" msgstr "" -#: templates/js/part.js:252 templates/js/part.js:450 -msgid "No parts found" +#: templates/js/part.js:75 templates/js/part.js:160 +msgid "Virtual part" msgstr "" -#: templates/js/part.js:304 templates/js/stock.js:409 templates/js/stock.js:966 -msgid "Select" -msgstr "" - -#: templates/js/part.js:361 +#: templates/js/part.js:87 msgid "Starred part" msgstr "" -#: templates/js/part.js:365 +#: templates/js/part.js:91 msgid "Salable part" msgstr "" -#: templates/js/part.js:404 +#: templates/js/part.js:205 +msgid "No variants found" +msgstr "" + +#: templates/js/part.js:291 templates/js/part.js:457 +msgid "No parts found" +msgstr "" + +#: templates/js/part.js:343 templates/js/stock.js:456 +#: templates/js/stock.js:1013 +msgid "Select" +msgstr "" + +#: templates/js/part.js:411 msgid "No category" msgstr "" -#: templates/js/part.js:422 templates/js/table_filters.js:251 +#: templates/js/part.js:429 templates/js/table_filters.js:256 msgid "Low stock" msgstr "" -#: templates/js/part.js:431 +#: templates/js/part.js:438 msgid "Building" msgstr "" -#: templates/js/part.js:510 +#: templates/js/part.js:517 msgid "YES" msgstr "" -#: templates/js/part.js:512 +#: templates/js/part.js:519 msgid "NO" msgstr "" -#: templates/js/part.js:546 +#: templates/js/part.js:553 msgid "No test templates matching query" msgstr "" -#: templates/js/part.js:597 templates/js/stock.js:63 +#: templates/js/part.js:604 templates/js/stock.js:63 msgid "Edit test result" msgstr "" -#: templates/js/part.js:598 templates/js/stock.js:64 +#: templates/js/part.js:605 templates/js/stock.js:64 msgid "Delete test result" msgstr "" -#: templates/js/part.js:604 +#: templates/js/part.js:611 msgid "This test is defined for a parent part" msgstr "" @@ -4550,71 +4809,75 @@ msgstr "" msgid "Test Date" msgstr "" -#: templates/js/stock.js:263 +#: templates/js/stock.js:281 +msgid "In production" +msgstr "" + +#: templates/js/stock.js:285 +msgid "Installed in Stock Item" +msgstr "" + +#: templates/js/stock.js:293 +msgid "Assigned to Sales Order" +msgstr "" + +#: templates/js/stock.js:313 msgid "No stock items matching query" msgstr "" -#: templates/js/stock.js:361 templates/js/stock.js:376 +#: templates/js/stock.js:424 msgid "Undefined location" msgstr "" -#: templates/js/stock.js:469 -msgid "Stock item has been allocated" -msgstr "" - -#: templates/js/stock.js:473 -msgid "Stock item has been assigned to customer" -msgstr "" - -#: templates/js/stock.js:476 -msgid "Stock item was assigned to a build order" -msgstr "" - -#: templates/js/stock.js:478 -msgid "Stock item was assigned to a sales order" -msgstr "" - -#: templates/js/stock.js:483 -msgid "Stock item has been installed in another item" -msgstr "" - -#: templates/js/stock.js:490 -msgid "Stock item has been rejected" -msgstr "" - -#: templates/js/stock.js:494 -msgid "Stock item is lost" -msgstr "" - -#: templates/js/stock.js:498 templates/js/table_filters.js:106 -msgid "Depleted" +#: templates/js/stock.js:518 +msgid "Stock item is in production" msgstr "" #: templates/js/stock.js:523 -msgid "Installed in Stock Item " +msgid "Stock item assigned to sales order" msgstr "" -#: templates/js/stock.js:531 -msgid "Assigned to sales order" +#: templates/js/stock.js:526 +msgid "Stock item assigned to customer" msgstr "" -#: templates/js/stock.js:709 +#: templates/js/stock.js:530 +msgid "Stock item has been allocated" +msgstr "" + +#: templates/js/stock.js:534 +msgid "Stock item has been installed in another item" +msgstr "" + +#: templates/js/stock.js:541 +msgid "Stock item has been rejected" +msgstr "" + +#: templates/js/stock.js:545 +msgid "Stock item is lost" +msgstr "" + +#: templates/js/stock.js:549 templates/js/table_filters.js:106 +msgid "Depleted" +msgstr "" + +#: templates/js/stock.js:743 msgid "No user information" msgstr "" -#: templates/js/stock.js:805 +#: templates/js/stock.js:852 msgid "Create New Location" msgstr "" -#: templates/js/stock.js:904 +#: templates/js/stock.js:951 msgid "Serial" msgstr "" -#: templates/js/stock.js:997 templates/js/table_filters.js:116 +#: templates/js/stock.js:1044 templates/js/table_filters.js:121 msgid "Installed" msgstr "" -#: templates/js/stock.js:1022 +#: templates/js/stock.js:1069 msgid "Install item" msgstr "" @@ -4626,36 +4889,36 @@ msgstr "" msgid "Validated" msgstr "" -#: templates/js/table_filters.js:65 templates/js/table_filters.js:126 +#: templates/js/table_filters.js:65 templates/js/table_filters.js:131 msgid "Is Serialized" msgstr "" -#: templates/js/table_filters.js:68 templates/js/table_filters.js:133 +#: templates/js/table_filters.js:68 templates/js/table_filters.js:138 msgid "Serial number GTE" msgstr "" -#: templates/js/table_filters.js:69 templates/js/table_filters.js:134 +#: templates/js/table_filters.js:69 templates/js/table_filters.js:139 msgid "Serial number greater than or equal to" msgstr "" -#: templates/js/table_filters.js:72 templates/js/table_filters.js:137 +#: templates/js/table_filters.js:72 templates/js/table_filters.js:142 msgid "Serial number LTE" msgstr "" -#: templates/js/table_filters.js:73 templates/js/table_filters.js:138 +#: templates/js/table_filters.js:73 templates/js/table_filters.js:143 msgid "Serial number less than or equal to" msgstr "" #: templates/js/table_filters.js:76 templates/js/table_filters.js:77 -#: templates/js/table_filters.js:129 templates/js/table_filters.js:130 +#: templates/js/table_filters.js:134 templates/js/table_filters.js:135 msgid "Serial number" msgstr "" -#: templates/js/table_filters.js:81 templates/js/table_filters.js:147 +#: templates/js/table_filters.js:81 templates/js/table_filters.js:152 msgid "Batch code" msgstr "" -#: templates/js/table_filters.js:91 templates/js/table_filters.js:218 +#: templates/js/table_filters.js:91 templates/js/table_filters.js:223 msgid "Active parts" msgstr "" @@ -4687,66 +4950,86 @@ msgstr "" msgid "Show items which are in stock" msgstr "" -#: templates/js/table_filters.js:117 -msgid "Show stock items which are installed in another item" +#: templates/js/table_filters.js:116 +msgid "In Production" msgstr "" -#: templates/js/table_filters.js:121 -msgid "Sent to customer" +#: templates/js/table_filters.js:117 +msgid "Show items which are in production" msgstr "" #: templates/js/table_filters.js:122 +msgid "Show stock items which are installed in another item" +msgstr "" + +#: templates/js/table_filters.js:126 +msgid "Sent to customer" +msgstr "" + +#: templates/js/table_filters.js:127 msgid "Show items which have been assigned to a customer" msgstr "" -#: templates/js/table_filters.js:142 templates/js/table_filters.js:143 +#: templates/js/table_filters.js:147 templates/js/table_filters.js:148 msgid "Stock status" msgstr "" -#: templates/js/table_filters.js:176 +#: templates/js/table_filters.js:181 msgid "Build status" msgstr "" -#: templates/js/table_filters.js:191 templates/js/table_filters.js:204 +#: templates/js/table_filters.js:196 templates/js/table_filters.js:209 msgid "Order status" msgstr "" -#: templates/js/table_filters.js:196 templates/js/table_filters.js:209 +#: templates/js/table_filters.js:201 templates/js/table_filters.js:214 msgid "Outstanding" msgstr "" -#: templates/js/table_filters.js:228 +#: templates/js/table_filters.js:233 msgid "Include subcategories" msgstr "" -#: templates/js/table_filters.js:229 +#: templates/js/table_filters.js:234 msgid "Include parts in subcategories" msgstr "" -#: templates/js/table_filters.js:233 +#: templates/js/table_filters.js:238 msgid "Has IPN" msgstr "" -#: templates/js/table_filters.js:234 +#: templates/js/table_filters.js:239 msgid "Part has internal part number" msgstr "" -#: templates/js/table_filters.js:239 +#: templates/js/table_filters.js:244 msgid "Show active parts" msgstr "" -#: templates/js/table_filters.js:247 +#: templates/js/table_filters.js:252 msgid "Stock available" msgstr "" -#: templates/js/table_filters.js:263 +#: templates/js/table_filters.js:268 msgid "Starred" msgstr "" -#: templates/js/table_filters.js:275 +#: templates/js/table_filters.js:280 msgid "Purchasable" msgstr "" +#: templates/modals.html:13 templates/modals.html:35 +msgid "Form errors exist" +msgstr "" + +#: templates/modals.html:18 templates/modals.html:40 +msgid "Close" +msgstr "" + +#: templates/modals.html:19 templates/modals.html:41 +msgid "Submit" +msgstr "" + #: templates/navbar.html:29 msgid "Buy" msgstr "" @@ -4811,10 +5094,6 @@ msgstr "" msgid "Order selected items" msgstr "" -#: templates/stock_table.html:25 -msgid "Order stock" -msgstr "" - #: templates/stock_table.html:28 msgid "Delete selected items" msgstr "" @@ -4843,38 +5122,38 @@ msgstr "" msgid "Important dates" msgstr "" -#: users/models.py:128 +#: users/models.py:129 msgid "Permission set" msgstr "" -#: users/models.py:136 +#: users/models.py:137 msgid "Group" msgstr "" -#: users/models.py:139 +#: users/models.py:140 msgid "View" msgstr "" -#: users/models.py:139 +#: users/models.py:140 msgid "Permission to view items" msgstr "" -#: users/models.py:141 +#: users/models.py:142 msgid "Add" msgstr "" -#: users/models.py:141 +#: users/models.py:142 msgid "Permission to add items" msgstr "" -#: users/models.py:143 +#: users/models.py:144 msgid "Change" msgstr "" -#: users/models.py:143 +#: users/models.py:144 msgid "Permissions to edit items" msgstr "" -#: users/models.py:145 +#: users/models.py:146 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 3f07b7337e..aacb4b74ec 100644 --- a/InvenTree/locale/es/LC_MESSAGES/django.po +++ b/InvenTree/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-10-30 04:52+0000\n" +"POT-Creation-Date: 2020-11-03 10:02+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -26,7 +26,7 @@ msgstr "" msgid "No matching action found" msgstr "" -#: InvenTree/forms.py:102 build/forms.py:49 +#: InvenTree/forms.py:102 build/forms.py:82 build/forms.py:170 msgid "Confirm" msgstr "" @@ -46,7 +46,7 @@ msgstr "" msgid "Apply Theme" msgstr "" -#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:261 +#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:269 msgid "Invalid quantity provided" msgstr "" @@ -86,12 +86,12 @@ msgstr "" msgid "File comment" msgstr "" -#: InvenTree/models.py:68 templates/js/stock.js:700 +#: InvenTree/models.py:68 templates/js/stock.js:734 msgid "User" msgstr "" #: InvenTree/models.py:106 part/templates/part/params.html:24 -#: templates/js/part.js:90 +#: templates/js/part.js:129 msgid "Name" msgstr "" @@ -116,7 +116,7 @@ msgid "Polish" msgstr "" #: InvenTree/status_codes.py:94 InvenTree/status_codes.py:135 -#: InvenTree/status_codes.py:222 templates/js/table_filters.js:181 +#: InvenTree/status_codes.py:222 msgid "Pending" msgstr "" @@ -168,10 +168,8 @@ msgstr "" msgid "Rejected" msgstr "" -#: InvenTree/status_codes.py:223 build/templates/build/allocate.html:358 -#: order/templates/order/sales_order_detail.html:225 -#: part/templates/part/tabs.html:23 templates/js/build.js:140 -msgid "Allocated" +#: InvenTree/status_codes.py:223 +msgid "Production" msgstr "" #: InvenTree/validators.py:39 @@ -204,7 +202,27 @@ msgstr "" msgid "Overage must be an integer value or a percentage" msgstr "" -#: InvenTree/views.py:752 +#: InvenTree/views.py:493 +msgid "Delete Item" +msgstr "" + +#: InvenTree/views.py:542 +msgid "Check box to confirm item deletion" +msgstr "" + +#: InvenTree/views.py:557 templates/InvenTree/settings/user.html:18 +msgid "Edit User Information" +msgstr "" + +#: InvenTree/views.py:568 templates/InvenTree/settings/user.html:22 +msgid "Set Password" +msgstr "" + +#: InvenTree/views.py:587 +msgid "Password fields must match" +msgstr "" + +#: InvenTree/views.py:757 msgid "Database Statistics" msgstr "" @@ -248,122 +266,190 @@ msgstr "" msgid "Barcode associated with StockItem" msgstr "" -#: build/forms.py:28 +#: build/forms.py:32 msgid "Build Order reference" msgstr "" -#: build/forms.py:70 -msgid "Location of completed parts" +#: 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 +#: order/templates/order/order_wizard/select_parts.html:32 +#: order/templates/order/purchase_order_detail.html:178 +#: order/templates/order/sales_order_detail.html:74 +#: 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 +#: stock/templates/stock/item_base.html:40 +#: stock/templates/stock/item_base.html:46 +#: stock/templates/stock/item_base.html:197 +#: stock/templates/stock/stock_adjust.html:18 templates/js/barcode.js:338 +#: templates/js/bom.js:189 templates/js/build.js:404 templates/js/stock.js:725 +#: templates/js/stock.js:953 +msgid "Quantity" msgstr "" -#: build/forms.py:74 +#: build/forms.py:71 +msgid "Enter quantity for build output" +msgstr "" + +#: build/forms.py:75 stock/forms.py:111 msgid "Serial numbers" msgstr "" -#: build/forms.py:76 stock/forms.py:111 -msgid "Enter unique serial numbers (or leave blank)" +#: build/forms.py:77 +msgid "Enter serial numbers for build outputs" msgstr "" -#: build/forms.py:79 +#: build/forms.py:83 +msgid "Confirm creation of build outut" +msgstr "" + +#: build/forms.py:103 +msgid "Confirm deletion of build output" +msgstr "" + +#: build/forms.py:124 +msgid "Confirm unallocation of stock" +msgstr "" + +#: build/forms.py:148 +msgid "Confirm stock allocation" +msgstr "" + +#: build/forms.py:171 +msgid "Mark build as complete" +msgstr "" + +#: build/forms.py:195 +msgid "Location of completed parts" +msgstr "" + +#: build/forms.py:200 +msgid "Confirm completion with incomplete stock allocation" +msgstr "" + +#: build/forms.py:203 msgid "Confirm build completion" msgstr "" -#: build/models.py:54 build/templates/build/build_base.html:8 +#: build/forms.py:223 build/views.py:68 +msgid "Confirm build cancellation" +msgstr "" + +#: build/forms.py:237 +msgid "Select quantity of stock to allocate" +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:214 +#: stock/templates/stock/item_base.html:227 msgid "Build Order" msgstr "" -#: build/models.py:55 build/templates/build/index.html:6 +#: 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 msgid "Build Orders" msgstr "" -#: build/models.py:77 -msgid "Build quantity must be integer value for trackable parts" -msgstr "" - -#: build/models.py:86 build/templates/build/build_base.html:73 +#: build/models.py:72 msgid "Build Order Reference" msgstr "" -#: build/models.py:87 build/templates/build/allocate.html:342 -#: order/templates/order/purchase_order_detail.html:173 templates/js/bom.js:195 +#: build/models.py:73 order/templates/order/purchase_order_detail.html:173 +#: templates/js/bom.js:181 templates/js/build.js:493 msgid "Reference" msgstr "" -#: build/models.py:94 build/templates/build/allocate.html:337 +#: build/models.py:80 build/templates/build/detail.html:19 #: company/templates/company/supplier_part_base.html:61 #: company/templates/company/supplier_part_detail.html:27 #: order/templates/order/purchase_order_detail.html:160 #: part/templates/part/detail.html:51 part/templates/part/set_category.html:14 -#: templates/InvenTree/search.html:147 templates/js/bom.js:188 -#: templates/js/bom.js:515 templates/js/build.js:56 templates/js/company.js:56 -#: templates/js/order.js:167 templates/js/order.js:249 templates/js/part.js:149 -#: templates/js/part.js:232 templates/js/part.js:384 templates/js/part.js:565 -#: templates/js/stock.js:445 templates/js/stock.js:672 +#: 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/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" msgstr "" -#: build/models.py:97 +#: build/models.py:83 msgid "Brief description of the build" msgstr "" -#: build/models.py:105 build/templates/build/build_base.html:94 +#: build/models.py:91 build/templates/build/build_base.html:94 +#: build/templates/build/detail.html:75 msgid "Parent Build" msgstr "" -#: build/models.py:106 -msgid "Parent build to which this build is allocated" +#: build/models.py:92 +msgid "BuildOrder to which this build is allocated" msgstr "" -#: build/models.py:111 build/templates/build/allocate.html:329 -#: build/templates/build/auto_allocate.html:19 -#: build/templates/build/build_base.html:78 -#: build/templates/build/detail.html:22 order/models.py:501 +#: 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 #: 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:293 #: part/templates/part/part_app_base.html:7 #: 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:500 -#: templates/js/build.js:61 templates/js/company.js:138 -#: templates/js/part.js:213 templates/js/part.js:318 templates/js/stock.js:421 -#: templates/js/stock.js:978 +#: templates/js/barcode.js:336 templates/js/bom.js:147 templates/js/bom.js:484 +#: templates/js/build.js:647 templates/js/company.js:138 +#: templates/js/part.js:252 templates/js/part.js:357 templates/js/stock.js:468 +#: templates/js/stock.js:1025 msgid "Part" msgstr "" -#: build/models.py:120 +#: build/models.py:105 msgid "Select part to build" msgstr "" -#: build/models.py:125 +#: build/models.py:110 msgid "Sales Order Reference" msgstr "" -#: build/models.py:129 +#: build/models.py:114 msgid "SalesOrder to which this build is allocated" msgstr "" -#: build/models.py:134 +#: build/models.py:119 msgid "Source Location" msgstr "" -#: build/models.py:138 +#: build/models.py:123 msgid "" "Select location to take stock from for this build (leave blank to take from " "any stock location)" msgstr "" -#: build/models.py:142 +#: build/models.py:128 +msgid "Destination Location" +msgstr "" + +#: build/models.py:132 +msgid "Select location where the completed items will be stored" +msgstr "" + +#: build/models.py:136 msgid "Build Quantity" msgstr "" +#: build/models.py:139 +msgid "Number of stock items to build" +msgstr "" + +#: build/models.py:143 +msgid "Completed items" +msgstr "" + #: build/models.py:145 -msgid "Number of parts to build" +msgid "Number of stock items which have been completed" msgstr "" #: build/models.py:149 part/templates/part/part_base.html:155 @@ -374,7 +460,7 @@ msgstr "" msgid "Build status code" msgstr "" -#: build/models.py:157 stock/models.py:387 +#: build/models.py:157 stock/models.py:389 msgid "Batch Code" msgstr "" @@ -382,15 +468,15 @@ msgstr "" msgid "Batch code for this build output" msgstr "" -#: build/models.py:176 build/templates/build/detail.html:55 +#: build/models.py:176 build/templates/build/detail.html:89 #: company/templates/company/supplier_part_base.html:68 #: company/templates/company/supplier_part_detail.html:24 #: part/templates/part/detail.html:80 part/templates/part/part_base.html:102 -#: stock/models.py:381 stock/templates/stock/item_base.html:266 +#: stock/models.py:383 stock/templates/stock/item_base.html:279 msgid "External Link" msgstr "" -#: build/models.py:177 part/models.py:565 stock/models.py:383 +#: build/models.py:177 part/models.py:596 stock/models.py:385 msgid "Link to external URL" msgstr "" @@ -398,10 +484,10 @@ 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:70 -#: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:453 -#: stock/models.py:1404 stock/templates/stock/tabs.html:26 -#: templates/js/barcode.js:391 templates/js/bom.js:264 -#: templates/js/stock.js:116 templates/js/stock.js:544 +#: 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 +#: templates/js/barcode.js:391 templates/js/bom.js:250 +#: templates/js/stock.js:116 templates/js/stock.js:578 msgid "Notes" msgstr "" @@ -409,138 +495,118 @@ msgstr "" msgid "Extra build notes" msgstr "" -#: build/models.py:520 +#: build/models.py:543 +msgid "No build output specified" +msgstr "" + +#: build/models.py:546 +msgid "Build output is already completed" +msgstr "" + +#: build/models.py:549 +msgid "Build output does not match Build Order" +msgstr "" + +#: build/models.py:620 +msgid "Completed build output" +msgstr "" + +#: build/models.py:858 +msgid "BuildItem must be unique for build, stock_item and install_into" +msgstr "" + +#: build/models.py:880 +msgid "Build item must specify a build output" +msgstr "" + +#: build/models.py:885 #, python-brace-format msgid "Selected stock item not found in BOM for part '{p}'" msgstr "" -#: build/models.py:523 +#: build/models.py:889 #, python-brace-format msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" msgstr "" -#: build/models.py:529 order/models.py:585 +#: build/models.py:896 order/models.py:603 msgid "StockItem is over-allocated" msgstr "" -#: build/models.py:532 order/models.py:588 +#: build/models.py:900 order/models.py:606 msgid "Allocation quantity must be greater than zero" msgstr "" -#: build/models.py:535 +#: build/models.py:904 msgid "Quantity must be 1 for serialized stock" msgstr "" -#: build/models.py:564 +#: build/models.py:944 msgid "Build to allocate parts" msgstr "" -#: build/models.py:571 -msgid "Stock Item to allocate to build" +#: build/models.py:951 +msgid "Source stock item" msgstr "" -#: build/models.py:584 +#: build/models.py:964 msgid "Stock quantity to allocate to build" msgstr "" -#: build/templates/build/allocate.html:17 -#: company/templates/company/detail_part.html:28 order/views.py:804 +#: build/models.py:972 +msgid "Destination stock item" +msgstr "" + +#: build/templates/build/allocate.html:14 +msgid "Incomplete Build Ouputs" +msgstr "" + +#: build/templates/build/allocate.html:20 +msgid "Build order has been completed" +msgstr "" + +#: build/templates/build/allocate.html:24 +msgid "Create new build output" +msgstr "" + +#: build/templates/build/allocate.html:25 +msgid "Create New Output" +msgstr "" + +#: build/templates/build/allocate.html:28 +msgid "Order required parts" +msgstr "" + +#: build/templates/build/allocate.html:29 +#: company/templates/company/detail_part.html:28 order/views.py:801 #: part/templates/part/category.html:125 msgid "Order Parts" msgstr "" -#: build/templates/build/allocate.html:18 -msgid "Automatically allocate stock" +#: build/templates/build/allocate.html:32 templates/js/build.js:574 +msgid "Unallocate stock" msgstr "" -#: build/templates/build/allocate.html:18 -msgid "Auto Allocate" +#: build/templates/build/allocate.html:33 build/views.py:341 build/views.py:778 +msgid "Unallocate Stock" msgstr "" -#: build/templates/build/allocate.html:19 -msgid "Unallocate" +#: build/templates/build/allocate.html:46 +msgid "Create a new build output" msgstr "" -#: build/templates/build/allocate.html:87 templates/stock_table.html:13 -msgid "New Stock Item" +#: build/templates/build/allocate.html:47 +msgid "No incomplete build outputs remain." msgstr "" -#: build/templates/build/allocate.html:88 stock/views.py:1459 -msgid "Create new Stock Item" +#: build/templates/build/allocate.html:48 +msgid "Create a new build output using the button above" msgstr "" -#: build/templates/build/allocate.html:170 -#: order/templates/order/sales_order_detail.html:72 -#: order/templates/order/sales_order_detail.html:154 stock/models.py:375 -#: stock/templates/stock/item_base.html:178 -msgid "Serial Number" -msgstr "" - -#: build/templates/build/allocate.html:172 -#: build/templates/build/auto_allocate.html:20 -#: build/templates/build/build_base.html:83 -#: build/templates/build/detail.html:27 -#: 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/sales_order_detail.html:74 -#: 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 -#: stock/templates/stock/item_base.html:26 -#: stock/templates/stock/item_base.html:32 -#: stock/templates/stock/item_base.html:184 -#: stock/templates/stock/stock_adjust.html:18 templates/js/barcode.js:338 -#: templates/js/bom.js:203 templates/js/build.js:72 templates/js/stock.js:691 -#: templates/js/stock.js:906 -msgid "Quantity" -msgstr "" - -#: build/templates/build/allocate.html:186 -#: build/templates/build/auto_allocate.html:21 stock/forms.py:336 -#: stock/templates/stock/item_base.html:220 -#: stock/templates/stock/stock_adjust.html:17 -#: templates/InvenTree/search.html:183 templates/js/barcode.js:337 -#: templates/js/stock.js:519 -msgid "Location" -msgstr "" - -#: build/templates/build/allocate.html:210 -#: order/templates/order/sales_order_detail.html:96 templates/js/build.js:144 -msgid "Edit stock allocation" -msgstr "" - -#: build/templates/build/allocate.html:211 -#: order/templates/order/sales_order_detail.html:97 templates/js/build.js:145 -msgid "Delete stock allocation" -msgstr "" - -#: build/templates/build/allocate.html:238 templates/js/bom.js:362 -msgid "No BOM items found" -msgstr "" - -#: build/templates/build/allocate.html:347 part/models.py:1425 -#: templates/js/part.js:569 templates/js/table_filters.js:167 -msgid "Required" -msgstr "" - -#: build/templates/build/allocate.html:356 -msgid "Assigned" -msgstr "" - -#: build/templates/build/allocate.html:394 -#: order/templates/order/sales_order_detail.html:275 -msgid "Buy parts" -msgstr "" - -#: build/templates/build/allocate.html:398 -#: order/templates/order/sales_order_detail.html:279 -msgid "Build parts" -msgstr "" - -#: build/templates/build/allocate.html:401 -msgid "Allocate stock" +#: build/templates/build/attachments.html:11 build/templates/build/tabs.html:17 +#: order/templates/order/po_tabs.html:11 order/templates/order/so_tabs.html:16 +#: part/templates/part/tabs.html:67 stock/templates/stock/tabs.html:32 +msgid "Attachments" msgstr "" #: build/templates/build/auto_allocate.html:9 @@ -549,19 +615,22 @@ msgstr "" #: build/templates/build/auto_allocate.html:10 msgid "" -"Stock Items are selected for automatic allocation if there is only a single " -"stock item available." +"The following stock items will be allocated to the specified build output" msgstr "" -#: build/templates/build/auto_allocate.html:11 -msgid "The following stock items will be allocated to the build:" +#: build/templates/build/auto_allocate.html:18 stock/forms.py:336 +#: stock/templates/stock/item_base.html:233 +#: 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 +msgid "Location" msgstr "" -#: build/templates/build/auto_allocate.html:40 +#: build/templates/build/auto_allocate.html:37 msgid "No stock items found that can be automatically allocated to this build" msgstr "" -#: build/templates/build/auto_allocate.html:42 +#: build/templates/build/auto_allocate.html:39 msgid "Stock items will have to be manually allocated" msgstr "" @@ -578,7 +647,7 @@ msgstr "" #: order/templates/order/order_base.html:26 #: order/templates/order/sales_order_base.html:35 #: part/templates/part/category.html:13 part/templates/part/part_base.html:32 -#: stock/templates/stock/item_base.html:76 +#: stock/templates/stock/item_base.html:90 #: stock/templates/stock/location.html:12 msgid "Admin view" msgstr "" @@ -587,7 +656,7 @@ msgstr "" msgid "Edit Build" msgstr "" -#: build/templates/build/build_base.html:50 build/views.py:190 +#: build/templates/build/build_base.html:50 msgid "Complete Build" msgstr "" @@ -595,7 +664,7 @@ msgstr "" msgid "Cancel Build" msgstr "" -#: build/templates/build/build_base.html:59 build/views.py:456 +#: build/templates/build/build_base.html:59 build/views.py:767 msgid "Delete Build" msgstr "" @@ -603,124 +672,163 @@ msgstr "" msgid "Build Details" msgstr "" -#: build/templates/build/build_base.html:88 -#: build/templates/build/detail.html:42 +#: 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:298 templates/InvenTree/search.html:175 -#: templates/js/barcode.js:42 templates/js/build.js:77 +#: stock/templates/stock/item_base.html:311 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:506 templates/js/stock.js:914 +#: templates/js/stock.js:557 templates/js/stock.js:961 msgid "Status" msgstr "" -#: build/templates/build/build_base.html:101 order/models.py:499 +#: build/templates/build/build_base.html:88 +#: build/templates/build/detail.html:62 +msgid "Progress" +msgstr "" + +#: build/templates/build/build_base.html:101 +#: build/templates/build/detail.html:82 order/models.py:517 #: 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:208 templates/js/order.js:221 +#: stock/templates/stock/item_base.html:221 templates/js/order.js:221 msgid "Sales Order" msgstr "" -#: build/templates/build/build_base.html:107 -msgid "BOM Price" -msgstr "" - -#: build/templates/build/build_base.html:112 -msgid "BOM pricing is incomplete" -msgstr "" - -#: build/templates/build/build_base.html:115 -msgid "No pricing information" -msgstr "" - #: build/templates/build/build_output.html:9 build/templates/build/tabs.html:11 msgid "Build Outputs" msgstr "" -#: build/templates/build/complete.html:6 -#: stock/templates/stock/item_base.html:245 templates/js/build.js:40 -#: templates/navbar.html:25 -msgid "Build" +#: build/templates/build/build_output_create.html:7 +msgid "The Bill of Materials contains trackable parts" msgstr "" -#: build/templates/build/complete.html:10 -msgid "Build order allocation is complete" +#: build/templates/build/build_output_create.html:8 +msgid "Build outputs must be generated individually." msgstr "" -#: build/templates/build/complete.html:14 -msgid "Warning: Build order allocation is not complete" +#: build/templates/build/build_output_create.html:9 +msgid "Multiple build outputs will be created based on the quantity specified." +msgstr "" + +#: build/templates/build/build_output_create.html:15 +msgid "Trackable parts can have serial numbers specified" +msgstr "" + +#: build/templates/build/build_output_create.html:16 +msgid "Enter serial numbers to generate multiple single build outputs" +msgstr "" + +#: build/templates/build/cancel.html:5 +msgid "Are you sure you wish to cancel this build?" +msgstr "" + +#: build/templates/build/complete.html:8 +msgid "Build can be completed" +msgstr "" + +#: build/templates/build/complete.html:12 +msgid "Build cannot be completed" msgstr "" #: build/templates/build/complete.html:15 -msgid "" -"Build Order has not been fully allocated. Ensure that all Stock Items have " -"been allocated to the Build" +msgid "Incompleted build outputs remain" msgstr "" -#: build/templates/build/complete.html:20 -msgid "The following actions will be performed:" +#: build/templates/build/complete.html:18 +msgid "Required build quantity has not been completed" msgstr "" -#: build/templates/build/complete.html:22 -msgid "Remove allocated items from stock" +#: build/templates/build/complete_output.html:9 +msgid "Stock allocation is complete" msgstr "" -#: build/templates/build/complete.html:23 -msgid "Add completed items to stock" +#: build/templates/build/complete_output.html:13 +msgid "Stock allocation is incomplete" msgstr "" -#: build/templates/build/complete.html:29 +#: build/templates/build/complete_output.html:19 +msgid "parts have not been fully allocated" +msgstr "" + +#: build/templates/build/complete_output.html:40 msgid "The following items will be created" msgstr "" -#: build/templates/build/delete_build_item.html:6 -msgid "Are you sure you want to unallocate these parts?" +#: build/templates/build/create_build_item.html:7 +msgid "Select a stock item to allocate to the selected build output" msgstr "" -#: build/templates/build/detail.html:17 -msgid "Title" +#: build/templates/build/create_build_item.html:11 +msgid "The allocated stock will be installed into the following build output:" msgstr "" -#: build/templates/build/detail.html:31 +#: build/templates/build/create_build_item.html:19 +msgid "No stock available for" +msgstr "" + +#: build/templates/build/delete_build_item.html:8 +msgid "Are you sure you want to unallocate this stock?" +msgstr "" + +#: build/templates/build/delete_build_item.html:11 +msgid "The selected stock will be unallocated from the build output" +msgstr "" + +#: build/templates/build/detail.html:33 msgid "Stock Source" msgstr "" -#: build/templates/build/detail.html:36 +#: build/templates/build/detail.html:38 msgid "Stock can be taken from any available location." msgstr "" -#: build/templates/build/detail.html:48 -#: stock/templates/stock/item_base.html:238 templates/js/stock.js:514 -#: templates/js/stock.js:921 templates/js/table_filters.js:80 -#: templates/js/table_filters.js:146 +#: build/templates/build/detail.html:44 stock/forms.py:364 +msgid "Destination" +msgstr "" + +#: build/templates/build/detail.html:51 +msgid "Destination location not specified" +msgstr "" + +#: build/templates/build/detail.html:68 +#: stock/templates/stock/item_base.html:251 templates/js/stock.js:565 +#: templates/js/stock.js:968 templates/js/table_filters.js:80 +#: templates/js/table_filters.js:151 msgid "Batch" msgstr "" -#: build/templates/build/detail.html:61 +#: build/templates/build/detail.html:95 #: order/templates/order/order_base.html:98 -#: order/templates/order/sales_order_base.html:100 templates/js/build.js:85 +#: order/templates/order/sales_order_base.html:100 templates/js/build.js:683 msgid "Created" msgstr "" -#: build/templates/build/detail.html:67 -msgid "Enough Parts?" +#: build/templates/build/detail.html:105 +msgid "BOM Price" msgstr "" -#: build/templates/build/detail.html:70 -msgid "Yes" +#: build/templates/build/detail.html:110 +msgid "BOM pricing is incomplete" msgstr "" -#: build/templates/build/detail.html:72 -msgid "No" +#: build/templates/build/detail.html:113 +msgid "No pricing information" msgstr "" -#: build/templates/build/detail.html:80 templates/js/build.js:90 +#: build/templates/build/detail.html:120 templates/js/build.js:661 +#: templates/js/build.js:688 msgid "Completed" msgstr "" -#: build/templates/build/index.html:25 build/views.py:403 +#: build/templates/build/edit_build_item.html:7 +msgid "Alter the quantity of stock allocated to the build output" +msgstr "" + +#: build/templates/build/index.html:25 build/views.py:658 msgid "New Build Order" msgstr "" @@ -748,94 +856,163 @@ msgid "Details" msgstr "" #: build/templates/build/tabs.html:8 -msgid "Allocated Parts" +msgid "Allocate Parts" msgstr "" -#: build/templates/build/unallocate.html:8 +#: build/templates/build/unallocate.html:10 msgid "Are you sure you wish to unallocate all stock for this build?" msgstr "" -#: build/views.py:77 -msgid "Confirm build cancellation" +#: build/templates/build/unallocate.html:12 +msgid "All incomplete stock allocations will be removed from the build" msgstr "" -#: build/views.py:82 +#: build/views.py:79 msgid "Build was cancelled" msgstr "" -#: build/views.py:98 +#: build/views.py:93 msgid "Allocate Stock" msgstr "" -#: build/views.py:112 -msgid "No matching build found" +#: build/views.py:157 build/views.py:317 build/views.py:490 +msgid "Build output must be specified" msgstr "" -#: build/views.py:131 -msgid "Confirm stock allocation" +#: build/views.py:171 +msgid "Allocated stock to build output" msgstr "" -#: build/views.py:132 -msgid "Check the confirmation box at the bottom of the list" +#: build/views.py:183 +msgid "Create Build Output" msgstr "" -#: build/views.py:152 build/views.py:467 -msgid "Unallocate Stock" +#: build/views.py:207 stock/models.py:832 stock/views.py:1645 +msgid "Serial numbers already exist" msgstr "" -#: build/views.py:166 +#: build/views.py:216 +msgid "Serial numbers required for trackable build output" +msgstr "" + +#: build/views.py:282 +msgid "Delete Build Output" +msgstr "" + +#: build/views.py:302 build/views.py:387 msgid "Confirm unallocation of build stock" msgstr "" -#: build/views.py:167 stock/views.py:421 +#: build/views.py:303 build/views.py:388 stock/views.py:413 msgid "Check the confirmation box" msgstr "" -#: build/views.py:270 -msgid "Confirm completion of build" +#: build/views.py:315 +msgid "Build output does not match build" msgstr "" -#: build/views.py:277 -msgid "Invalid location selected" +#: build/views.py:329 +msgid "Build output deleted" msgstr "" -#: build/views.py:302 stock/views.py:1653 -#, python-brace-format -msgid "The following serial numbers already exist: ({sn})" +#: build/views.py:412 +msgid "Complete Build Order" msgstr "" -#: build/views.py:323 -msgid "Build marked as COMPLETE" +#: build/views.py:418 +msgid "Build order cannot be completed" msgstr "" -#: build/views.py:431 +#: build/views.py:429 +msgid "Completed build order" +msgstr "" + +#: build/views.py:445 +msgid "Complete Build Output" +msgstr "" + +#: build/views.py:481 +msgid "Quantity to complete cannot exceed build output quantity" +msgstr "" + +#: build/views.py:487 +msgid "Confirm completion of incomplete build" +msgstr "" + +#: build/views.py:578 +msgid "Build output completed" +msgstr "" + +#: build/views.py:703 msgid "Created new build" msgstr "" -#: build/views.py:441 +#: build/views.py:724 msgid "Edit Build Details" msgstr "" -#: build/views.py:447 +#: build/views.py:758 msgid "Edited build" msgstr "" -#: build/views.py:473 +#: build/views.py:784 msgid "Removed parts from build allocation" msgstr "" -#: build/views.py:483 -msgid "Allocate new Part" +#: build/views.py:796 +msgid "Allocate stock to build output" msgstr "" -#: build/views.py:637 +#: build/views.py:840 +msgid "Item must be currently in stock" +msgstr "" + +#: build/views.py:846 +msgid "Stock item is over-allocated" +msgstr "" + +#: build/views.py:847 templates/js/bom.js:215 templates/js/build.js:503 +#: templates/js/build.js:731 +msgid "Available" +msgstr "" + +#: build/views.py:849 +msgid "Stock item must be selected" +msgstr "" + +#: build/views.py:1011 msgid "Edit Stock Allocation" msgstr "" -#: build/views.py:642 +#: build/views.py:1016 msgid "Updated Build Item" msgstr "" +#: build/views.py:1045 +msgid "Add Build Order Attachment" +msgstr "" + +#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:96 +#: stock/views.py:176 +msgid "Added attachment" +msgstr "" + +#: build/views.py:1095 order/views.py:191 order/views.py:213 +msgid "Edit Attachment" +msgstr "" + +#: build/views.py:1106 order/views.py:196 order/views.py:218 +msgid "Attachment updated" +msgstr "" + +#: build/views.py:1116 order/views.py:233 order/views.py:248 +msgid "Delete Attachment" +msgstr "" + +#: build/views.py:1122 order/views.py:240 order/views.py:255 stock/views.py:234 +msgid "Deleted attachment" +msgstr "" + #: common/models.py:51 msgid "InvenTree Instance Name" msgstr "" @@ -1041,8 +1218,8 @@ msgstr "" msgid "Does this company manufacture parts?" msgstr "" -#: company/models.py:283 stock/models.py:335 -#: stock/templates/stock/item_base.html:164 +#: company/models.py:283 stock/models.py:337 +#: stock/templates/stock/item_base.html:177 msgid "Base Part" msgstr "" @@ -1083,7 +1260,7 @@ msgid "Part packaging" msgstr "" #: company/templates/company/assigned_stock.html:9 -#: company/templates/company/tabs.html:25 +#: company/templates/company/tabs.html:25 templates/js/build.js:395 msgid "Assigned Stock" msgstr "" @@ -1113,14 +1290,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:273 templates/js/company.js:48 +#: stock/templates/stock/item_base.html:286 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:370 -#: stock/models.py:371 stock/templates/stock/item_base.html:191 +#: order/templates/order/sales_order_base.html:81 stock/models.py:372 +#: stock/models.py:373 stock/templates/stock/item_base.html:204 #: templates/js/company.js:40 templates/js/order.js:236 msgid "Customer" msgstr "" @@ -1136,7 +1313,7 @@ msgstr "" #: company/templates/company/detail_part.html:18 #: order/templates/order/purchase_order_detail.html:68 -#: part/templates/part/supplier.html:14 templates/js/stock.js:798 +#: part/templates/part/supplier.html:14 templates/js/stock.js:845 msgid "New Supplier Part" msgstr "" @@ -1160,7 +1337,7 @@ msgid "Delete Parts" msgstr "" #: company/templates/company/detail_part.html:63 -#: part/templates/part/category.html:116 templates/js/stock.js:792 +#: part/templates/part/category.html:116 templates/js/stock.js:839 msgid "New Part" msgstr "" @@ -1192,7 +1369,7 @@ msgstr "" #: company/templates/company/detail_stock.html:35 #: company/templates/company/supplier_part_stock.html:33 -#: part/templates/part/bom.html:67 part/templates/part/category.html:112 +#: part/templates/part/bom.html:63 part/templates/part/category.html:112 #: part/templates/part/category.html:126 part/templates/part/stock.html:51 #: templates/stock_table.html:7 msgid "Export" @@ -1252,8 +1429,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:344 -#: stock/templates/stock/item_base.html:278 templates/js/company.js:180 +#: company/templates/company/supplier_part_base.html:19 stock/models.py:346 +#: stock/templates/stock/item_base.html:291 templates/js/company.js:180 msgid "Supplier Part" msgstr "" @@ -1310,7 +1487,7 @@ 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:2281 +#: part/templates/part/sale_prices.html:13 part/views.py:2292 msgid "Add Price Break" msgstr "" @@ -1320,7 +1497,7 @@ 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:248 +#: part/templates/part/sale_prices.html:85 templates/js/bom.js:234 msgid "Price" msgstr "" @@ -1345,8 +1522,8 @@ 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:153 -#: templates/js/part.js:411 templates/js/stock.js:453 templates/navbar.html:22 +#: 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" msgstr "" @@ -1429,7 +1606,7 @@ msgstr "" msgid "Edit Supplier Part" msgstr "" -#: company/views.py:278 templates/js/stock.js:799 +#: company/views.py:278 templates/js/stock.js:846 msgid "Create new Supplier Part" msgstr "" @@ -1437,15 +1614,15 @@ msgstr "" msgid "Delete Supplier Part" msgstr "" -#: company/views.py:416 part/views.py:2287 +#: company/views.py:416 part/views.py:2298 msgid "Added new price break" msgstr "" -#: company/views.py:453 part/views.py:2332 +#: company/views.py:453 part/views.py:2343 msgid "Edit Price Break" msgstr "" -#: company/views.py:469 part/views.py:2348 +#: company/views.py:469 part/views.py:2359 msgid "Delete Price Break" msgstr "" @@ -1518,7 +1695,7 @@ msgstr "" msgid "Order notes" msgstr "" -#: order/models.py:140 order/models.py:318 +#: order/models.py:140 order/models.py:326 msgid "Purchase order status" msgstr "" @@ -1538,8 +1715,8 @@ msgstr "" msgid "Date order was completed" msgstr "" -#: order/models.py:185 order/models.py:259 part/views.py:1398 -#: stock/models.py:241 stock/models.py:805 +#: order/models.py:185 order/models.py:267 part/views.py:1409 +#: stock/models.py:243 stock/models.py:816 msgid "Quantity must be greater than zero" msgstr "" @@ -1547,69 +1724,69 @@ msgstr "" msgid "Part supplier must match PO supplier" msgstr "" -#: order/models.py:254 +#: order/models.py:262 msgid "Lines can only be received against an order marked as 'Placed'" msgstr "" -#: order/models.py:314 +#: order/models.py:322 msgid "Company to which the items are being sold" msgstr "" -#: order/models.py:320 +#: order/models.py:328 msgid "Customer order reference code" msgstr "" -#: order/models.py:359 +#: order/models.py:367 msgid "SalesOrder cannot be shipped as it is not currently pending" msgstr "" -#: order/models.py:436 +#: order/models.py:454 msgid "Item quantity" msgstr "" -#: order/models.py:438 +#: order/models.py:456 msgid "Line item reference" msgstr "" -#: order/models.py:440 +#: order/models.py:458 msgid "Line item notes" msgstr "" -#: order/models.py:466 order/templates/order/order_base.html:9 +#: order/models.py:484 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 -#: stock/templates/stock/item_base.html:252 templates/js/order.js:139 +#: stock/templates/stock/item_base.html:265 templates/js/order.js:139 msgid "Purchase Order" msgstr "" -#: order/models.py:479 +#: order/models.py:497 msgid "Supplier part" msgstr "" -#: order/models.py:482 +#: order/models.py:500 msgid "Number of items received" msgstr "" -#: order/models.py:576 +#: order/models.py:594 msgid "Cannot allocate stock item to a line with a different part" msgstr "" -#: order/models.py:578 +#: order/models.py:596 msgid "Cannot allocate stock to a line without a part" msgstr "" -#: order/models.py:581 +#: order/models.py:599 msgid "Allocation quantity cannot exceed stock quantity" msgstr "" -#: order/models.py:591 +#: order/models.py:609 msgid "Quantity must be 1 for serialized stock item" msgstr "" -#: order/models.py:608 +#: order/models.py:626 msgid "Select stock item to allocate" msgstr "" -#: order/models.py:611 +#: order/models.py:629 msgid "Enter stock allocation quantity" msgstr "" @@ -1726,14 +1903,9 @@ msgstr "" msgid "Line Items" msgstr "" -#: order/templates/order/po_tabs.html:11 order/templates/order/so_tabs.html:16 -#: part/templates/part/tabs.html:67 stock/templates/stock/tabs.html:32 -msgid "Attachments" -msgstr "" - #: order/templates/order/purchase_order_detail.html:17 -#: order/templates/order/sales_order_detail.html:19 order/views.py:1117 -#: order/views.py:1232 +#: order/templates/order/sales_order_detail.html:19 order/views.py:1115 +#: order/views.py:1199 msgid "Add Line Item" msgstr "" @@ -1744,7 +1916,7 @@ msgstr "" #: order/templates/order/purchase_order_detail.html:39 #: order/templates/order/purchase_order_detail.html:119 #: part/templates/part/category.html:173 part/templates/part/category.html:215 -#: templates/js/stock.js:804 +#: templates/js/stock.js:851 msgid "New Location" msgstr "" @@ -1785,7 +1957,7 @@ msgid "Select parts to receive against this order" msgstr "" #: order/templates/order/receive_parts.html:21 -#: part/templates/part/part_base.html:145 templates/js/part.js:427 +#: part/templates/part/part_base.html:145 templates/js/part.js:434 msgid "On Order" msgstr "" @@ -1824,10 +1996,40 @@ msgstr "" 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 +#: stock/templates/stock/item_base.html:191 templates/js/build.js:402 +msgid "Serial Number" +msgstr "" + +#: order/templates/order/sales_order_detail.html:96 templates/js/build.js:443 +#: templates/js/build.js:742 +msgid "Edit stock allocation" +msgstr "" + +#: order/templates/order/sales_order_detail.html:97 templates/js/build.js:445 +#: templates/js/build.js:743 +msgid "Delete stock allocation" +msgstr "" + +#: order/templates/order/sales_order_detail.html:225 +#: part/templates/part/tabs.html:23 templates/js/build.js:507 +#: templates/js/build.js:738 +msgid "Allocated" +msgstr "" + #: order/templates/order/sales_order_detail.html:227 msgid "Fulfilled" msgstr "" +#: order/templates/order/sales_order_detail.html:275 +msgid "Buy parts" +msgstr "" + +#: order/templates/order/sales_order_detail.html:279 +msgid "Build parts" +msgstr "" + #: order/templates/order/sales_order_detail.html:282 msgid "Allocate parts" msgstr "" @@ -1878,143 +2080,131 @@ msgstr "" msgid "Add Purchase Order Attachment" msgstr "" -#: order/views.py:109 order/views.py:157 part/views.py:92 stock/views.py:175 -msgid "Added attachment" -msgstr "" - -#: order/views.py:148 +#: order/views.py:150 msgid "Add Sales Order Attachment" msgstr "" -#: order/views.py:184 order/views.py:206 -msgid "Edit Attachment" -msgstr "" - -#: order/views.py:189 order/views.py:211 -msgid "Attachment updated" -msgstr "" - -#: order/views.py:226 order/views.py:241 -msgid "Delete Attachment" -msgstr "" - -#: order/views.py:233 order/views.py:248 stock/views.py:233 -msgid "Deleted attachment" -msgstr "" - -#: order/views.py:301 +#: order/views.py:310 msgid "Create Purchase Order" msgstr "" -#: order/views.py:333 +#: order/views.py:345 msgid "Create Sales Order" msgstr "" -#: order/views.py:364 +#: order/views.py:380 msgid "Edit Purchase Order" msgstr "" -#: order/views.py:385 +#: order/views.py:401 msgid "Edit Sales Order" msgstr "" -#: order/views.py:402 +#: order/views.py:418 msgid "Cancel Order" msgstr "" -#: order/views.py:418 order/views.py:451 +#: order/views.py:428 order/views.py:455 msgid "Confirm order cancellation" msgstr "" -#: order/views.py:436 +#: order/views.py:431 order/views.py:458 +msgid "Order cannot be cancelled" +msgstr "" + +#: order/views.py:445 msgid "Cancel sales order" msgstr "" -#: order/views.py:457 -msgid "Could not cancel order" -msgstr "" - -#: order/views.py:471 +#: order/views.py:472 msgid "Issue Order" msgstr "" -#: order/views.py:487 +#: order/views.py:482 msgid "Confirm order placement" msgstr "" -#: order/views.py:508 +#: order/views.py:492 +msgid "Purchase order issued" +msgstr "" + +#: order/views.py:503 msgid "Complete Order" msgstr "" -#: order/views.py:544 +#: order/views.py:520 +msgid "Confirm order completion" +msgstr "" + +#: order/views.py:531 +msgid "Purchase order completed" +msgstr "" + +#: order/views.py:541 msgid "Ship Order" msgstr "" -#: order/views.py:561 +#: order/views.py:558 msgid "Confirm order shipment" msgstr "" -#: order/views.py:567 +#: order/views.py:564 msgid "Could not ship order" msgstr "" -#: order/views.py:619 +#: order/views.py:616 msgid "Receive Parts" msgstr "" -#: order/views.py:687 +#: order/views.py:684 msgid "Items received" msgstr "" -#: order/views.py:701 +#: order/views.py:698 msgid "No destination set" msgstr "" -#: order/views.py:746 +#: order/views.py:743 msgid "Error converting quantity to number" msgstr "" -#: order/views.py:752 +#: order/views.py:749 msgid "Receive quantity less than zero" msgstr "" -#: order/views.py:758 +#: order/views.py:755 msgid "No lines specified" msgstr "" -#: order/views.py:1138 -msgid "Invalid Purchase Order" +#: order/views.py:1125 +msgid "Supplier part must be specified" msgstr "" -#: order/views.py:1146 +#: order/views.py:1131 msgid "Supplier must match for Part and Order" msgstr "" -#: order/views.py:1151 -msgid "Invalid SupplierPart selection" -msgstr "" - -#: order/views.py:1284 order/views.py:1303 +#: order/views.py:1251 order/views.py:1270 msgid "Edit Line Item" msgstr "" -#: order/views.py:1320 order/views.py:1333 +#: order/views.py:1287 order/views.py:1300 msgid "Delete Line Item" msgstr "" -#: order/views.py:1326 order/views.py:1339 +#: order/views.py:1293 order/views.py:1306 msgid "Deleted line item" msgstr "" -#: order/views.py:1348 +#: order/views.py:1315 msgid "Allocate Stock to Order" msgstr "" -#: order/views.py:1418 +#: order/views.py:1385 msgid "Edit Allocation Quantity" msgstr "" -#: order/views.py:1434 +#: order/views.py:1401 msgid "Remove allocation" msgstr "" @@ -2088,7 +2278,7 @@ msgstr "" msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:93 part/models.py:1504 +#: part/forms.py:93 part/models.py:1582 msgid "Parent Part" msgstr "" @@ -2166,208 +2356,213 @@ msgstr "" msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" msgstr "" -#: part/models.py:435 +#: part/models.py:452 msgid "Next available serial numbers are" msgstr "" -#: part/models.py:439 +#: part/models.py:456 msgid "Next available serial number is" msgstr "" -#: part/models.py:444 +#: part/models.py:461 msgid "Most recent serial number is" msgstr "" -#: part/models.py:522 +#: part/models.py:539 msgid "Part must be unique for name, IPN and revision" msgstr "" -#: part/models.py:537 part/templates/part/detail.html:19 +#: part/models.py:568 part/templates/part/detail.html:19 msgid "Part name" msgstr "" -#: part/models.py:541 +#: part/models.py:572 msgid "Is this part a template part?" msgstr "" -#: part/models.py:550 +#: part/models.py:581 msgid "Is this part a variant of another part?" msgstr "" -#: part/models.py:552 +#: part/models.py:583 msgid "Part description" msgstr "" -#: part/models.py:554 +#: part/models.py:585 msgid "Part keywords to improve visibility in search results" msgstr "" -#: part/models.py:559 +#: part/models.py:590 msgid "Part category" msgstr "" -#: part/models.py:561 +#: part/models.py:592 msgid "Internal Part Number" msgstr "" -#: part/models.py:563 +#: part/models.py:594 msgid "Part revision or version number" msgstr "" -#: part/models.py:577 +#: part/models.py:608 msgid "Where is this item normally stored?" msgstr "" -#: part/models.py:621 +#: part/models.py:652 msgid "Default supplier part" msgstr "" -#: part/models.py:624 +#: part/models.py:655 msgid "Minimum allowed stock level" msgstr "" -#: part/models.py:626 +#: part/models.py:657 msgid "Stock keeping units for this part" msgstr "" -#: part/models.py:628 +#: part/models.py:659 msgid "Can this part be built from other parts?" msgstr "" -#: part/models.py:630 +#: part/models.py:661 msgid "Can this part be used to build other parts?" msgstr "" -#: part/models.py:632 +#: part/models.py:663 msgid "Does this part have tracking for unique items?" msgstr "" -#: part/models.py:634 +#: part/models.py:665 msgid "Can this part be purchased from external suppliers?" msgstr "" -#: part/models.py:636 +#: part/models.py:667 msgid "Can this part be sold to customers?" msgstr "" -#: part/models.py:638 +#: part/models.py:669 msgid "Is this part active?" msgstr "" -#: part/models.py:640 +#: part/models.py:671 msgid "Is this a virtual part, such as a software product or license?" msgstr "" -#: part/models.py:642 +#: part/models.py:673 msgid "Part notes - supports Markdown formatting" msgstr "" -#: part/models.py:644 +#: part/models.py:675 msgid "Stored BOM checksum" msgstr "" -#: part/models.py:1377 +#: part/models.py:1455 msgid "Test templates can only be created for trackable parts" msgstr "" -#: part/models.py:1394 +#: part/models.py:1472 msgid "Test with this name already exists for this part" msgstr "" -#: part/models.py:1413 templates/js/part.js:560 templates/js/stock.js:92 +#: part/models.py:1491 templates/js/part.js:567 templates/js/stock.js:92 msgid "Test Name" msgstr "" -#: part/models.py:1414 +#: part/models.py:1492 msgid "Enter a name for the test" msgstr "" -#: part/models.py:1419 +#: part/models.py:1497 msgid "Test Description" msgstr "" -#: part/models.py:1420 +#: part/models.py:1498 msgid "Enter description for this test" msgstr "" -#: part/models.py:1426 +#: part/models.py:1503 templates/js/part.js:576 +#: templates/js/table_filters.js:172 +msgid "Required" +msgstr "" + +#: part/models.py:1504 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1431 templates/js/part.js:577 +#: part/models.py:1509 templates/js/part.js:584 msgid "Requires Value" msgstr "" -#: part/models.py:1432 +#: part/models.py:1510 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1437 templates/js/part.js:584 +#: part/models.py:1515 templates/js/part.js:591 msgid "Requires Attachment" msgstr "" -#: part/models.py:1438 +#: part/models.py:1516 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1471 +#: part/models.py:1549 msgid "Parameter template name must be unique" msgstr "" -#: part/models.py:1476 +#: part/models.py:1554 msgid "Parameter Name" msgstr "" -#: part/models.py:1478 +#: part/models.py:1556 msgid "Parameter Units" msgstr "" -#: part/models.py:1506 +#: part/models.py:1584 msgid "Parameter Template" msgstr "" -#: part/models.py:1508 +#: part/models.py:1586 msgid "Parameter Value" msgstr "" -#: part/models.py:1545 +#: part/models.py:1623 msgid "Select parent part" msgstr "" -#: part/models.py:1553 +#: part/models.py:1631 msgid "Select part to be used in BOM" msgstr "" -#: part/models.py:1559 +#: part/models.py:1637 msgid "BOM quantity for this BOM item" msgstr "" -#: part/models.py:1561 +#: part/models.py:1639 msgid "This BOM item is optional" msgstr "" -#: part/models.py:1564 +#: part/models.py:1642 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "" -#: part/models.py:1567 +#: part/models.py:1645 msgid "BOM item reference" msgstr "" -#: part/models.py:1570 +#: part/models.py:1648 msgid "BOM item notes" msgstr "" -#: part/models.py:1572 +#: part/models.py:1650 msgid "BOM line checksum" msgstr "" -#: part/models.py:1636 part/views.py:1404 part/views.py:1456 -#: stock/models.py:231 +#: part/models.py:1717 part/views.py:1415 part/views.py:1467 +#: stock/models.py:233 msgid "Quantity must be integer value for trackable parts" msgstr "" -#: part/models.py:1645 +#: part/models.py:1733 msgid "BOM Item" msgstr "" @@ -2385,10 +2580,10 @@ msgstr "" #: part/templates/part/allocation.html:28 #: part/templates/part/allocation.html:45 #: stock/templates/stock/item_base.html:8 -#: stock/templates/stock/item_base.html:58 -#: stock/templates/stock/item_base.html:260 -#: stock/templates/stock/stock_adjust.html:16 templates/js/build.js:126 -#: templates/js/stock.js:661 templates/js/stock.js:897 +#: stock/templates/stock/item_base.html:72 +#: stock/templates/stock/item_base.html:273 +#: 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" msgstr "" @@ -2400,68 +2595,68 @@ msgstr "" msgid "Bill of Materials" msgstr "" -#: part/templates/part/bom.html:38 +#: part/templates/part/bom.html:34 msgid "Remove selected BOM items" msgstr "" -#: part/templates/part/bom.html:41 +#: part/templates/part/bom.html:37 msgid "Import BOM data" msgstr "" -#: part/templates/part/bom.html:42 +#: part/templates/part/bom.html:38 msgid "Import from File" msgstr "" -#: part/templates/part/bom.html:45 +#: part/templates/part/bom.html:41 msgid "Copy BOM from parent part" msgstr "" -#: part/templates/part/bom.html:46 +#: part/templates/part/bom.html:42 msgid "Copy from Parent" msgstr "" -#: part/templates/part/bom.html:49 +#: part/templates/part/bom.html:45 msgid "New BOM Item" msgstr "" -#: part/templates/part/bom.html:50 +#: part/templates/part/bom.html:46 msgid "Add Item" msgstr "" -#: part/templates/part/bom.html:52 +#: part/templates/part/bom.html:48 msgid "Finish Editing" msgstr "" -#: part/templates/part/bom.html:53 +#: part/templates/part/bom.html:49 msgid "Finished" msgstr "" -#: part/templates/part/bom.html:57 +#: part/templates/part/bom.html:53 msgid "Edit BOM" msgstr "" -#: part/templates/part/bom.html:58 part/templates/part/params.html:38 +#: part/templates/part/bom.html:54 part/templates/part/params.html:38 #: templates/InvenTree/settings/user.html:19 msgid "Edit" msgstr "" -#: part/templates/part/bom.html:61 +#: part/templates/part/bom.html:57 msgid "Validate Bill of Materials" msgstr "" -#: part/templates/part/bom.html:62 +#: part/templates/part/bom.html:58 msgid "Validate" msgstr "" -#: part/templates/part/bom.html:66 part/views.py:1695 +#: part/templates/part/bom.html:62 part/views.py:1706 msgid "Export Bill of Materials" msgstr "" -#: part/templates/part/bom.html:127 +#: part/templates/part/bom.html:123 msgid "Delete selected BOM items?" msgstr "" -#: part/templates/part/bom.html:128 +#: part/templates/part/bom.html:124 msgid "All selected BOM items will be deleted" msgstr "" @@ -2541,7 +2736,7 @@ msgstr "" msgid "Part Builds" msgstr "" -#: part/templates/part/build.html:14 +#: part/templates/part/build.html:15 msgid "Start New Build" msgstr "" @@ -2549,7 +2744,7 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:24 part/views.py:2098 +#: part/templates/part/category.html:24 part/views.py:2109 msgid "Create new part category" msgstr "" @@ -2621,7 +2816,7 @@ msgstr "" msgid "Create new Part Category" msgstr "" -#: part/templates/part/category.html:216 stock/views.py:1343 +#: part/templates/part/category.html:216 stock/views.py:1338 msgid "Create new Stock Location" msgstr "" @@ -2646,11 +2841,11 @@ msgid "Part Details" msgstr "" #: part/templates/part/detail.html:25 part/templates/part/part_base.html:95 -#: templates/js/part.js:141 +#: templates/js/part.js:180 msgid "IPN" msgstr "" -#: part/templates/part/detail.html:32 templates/js/part.js:145 +#: part/templates/part/detail.html:32 templates/js/part.js:184 msgid "Revision" msgstr "" @@ -2667,7 +2862,7 @@ msgid "Variant Of" msgstr "" #: part/templates/part/detail.html:70 part/templates/part/set_category.html:15 -#: templates/js/part.js:398 +#: templates/js/part.js:405 msgid "Category" msgstr "" @@ -2708,7 +2903,7 @@ msgid "Part is not a virtual part" msgstr "" #: part/templates/part/detail.html:148 stock/forms.py:248 -#: templates/js/table_filters.js:23 templates/js/table_filters.js:243 +#: templates/js/table_filters.js:23 templates/js/table_filters.js:248 msgid "Template" msgstr "" @@ -2720,7 +2915,7 @@ msgstr "" msgid "Part is not a template part" msgstr "" -#: part/templates/part/detail.html:158 templates/js/table_filters.js:255 +#: part/templates/part/detail.html:158 templates/js/table_filters.js:260 msgid "Assembly" msgstr "" @@ -2732,7 +2927,7 @@ msgstr "" msgid "Part cannot be assembled from other parts" msgstr "" -#: part/templates/part/detail.html:168 templates/js/table_filters.js:259 +#: part/templates/part/detail.html:168 templates/js/table_filters.js:264 msgid "Component" msgstr "" @@ -2745,7 +2940,7 @@ msgid "Part cannot be used in assemblies" msgstr "" #: part/templates/part/detail.html:178 templates/js/table_filters.js:31 -#: templates/js/table_filters.js:271 +#: templates/js/table_filters.js:276 msgid "Trackable" msgstr "" @@ -2765,7 +2960,7 @@ msgstr "" msgid "Part can be purchased from external suppliers" msgstr "" -#: part/templates/part/detail.html:198 templates/js/table_filters.js:267 +#: part/templates/part/detail.html:198 templates/js/table_filters.js:272 msgid "Salable" msgstr "" @@ -2778,7 +2973,8 @@ msgid "Part cannot be sold to customers" msgstr "" #: part/templates/part/detail.html:214 templates/js/table_filters.js:19 -#: templates/js/table_filters.js:55 templates/js/table_filters.js:238 +#: templates/js/table_filters.js:55 templates/js/table_filters.js:186 +#: templates/js/table_filters.js:243 msgid "Active" msgstr "" @@ -2806,13 +3002,13 @@ msgstr "" msgid "New Parameter" msgstr "" -#: part/templates/part/params.html:25 stock/models.py:1391 +#: part/templates/part/params.html:25 stock/models.py:1415 #: templates/js/stock.js:112 msgid "Value" msgstr "" #: part/templates/part/params.html:41 part/templates/part/supplier.html:19 -#: users/models.py:145 +#: users/models.py:146 msgid "Delete" msgstr "" @@ -2832,9 +3028,8 @@ msgstr "" msgid "This part is a variant of" msgstr "" -#: part/templates/part/part_base.html:36 templates/js/bom.js:175 -#: templates/js/company.js:155 templates/js/part.js:133 -#: templates/js/part.js:375 +#: part/templates/part/part_base.html:36 templates/js/company.js:155 +#: templates/js/part.js:95 templates/js/part.js:172 msgid "Inactive" msgstr "" @@ -2843,19 +3038,19 @@ msgid "Star this part" msgstr "" #: part/templates/part/part_base.html:49 -#: stock/templates/stock/item_base.html:88 +#: stock/templates/stock/item_base.html:101 #: stock/templates/stock/location.html:29 msgid "Barcode actions" msgstr "" #: part/templates/part/part_base.html:51 -#: stock/templates/stock/item_base.html:90 +#: stock/templates/stock/item_base.html:103 #: stock/templates/stock/location.html:31 msgid "Show QR Code" msgstr "" #: part/templates/part/part_base.html:52 -#: stock/templates/stock/item_base.html:91 +#: stock/templates/stock/item_base.html:104 #: stock/templates/stock/location.html:32 msgid "Print Label" msgstr "" @@ -2944,8 +3139,8 @@ msgstr "" msgid "Part Stock" msgstr "" -#: part/templates/part/stock_count.html:7 templates/js/bom.js:238 -#: templates/js/part.js:435 +#: part/templates/part/stock_count.html:7 templates/js/bom.js:224 +#: templates/js/part.js:442 msgid "No Stock" msgstr "" @@ -2985,7 +3180,7 @@ msgstr "" msgid "Used In" msgstr "" -#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:304 +#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:317 msgid "Tests" msgstr "" @@ -3013,184 +3208,196 @@ msgstr "" msgid "Add part attachment" msgstr "" -#: part/views.py:131 templates/attachment_table.html:34 +#: part/views.py:135 templates/attachment_table.html:34 msgid "Edit attachment" msgstr "" -#: part/views.py:137 +#: part/views.py:141 msgid "Part attachment updated" msgstr "" -#: part/views.py:152 +#: part/views.py:156 msgid "Delete Part Attachment" msgstr "" -#: part/views.py:160 +#: part/views.py:164 msgid "Deleted part attachment" msgstr "" -#: part/views.py:169 +#: part/views.py:173 msgid "Create Test Template" msgstr "" -#: part/views.py:198 +#: part/views.py:202 msgid "Edit Test Template" msgstr "" -#: part/views.py:214 +#: part/views.py:218 msgid "Delete Test Template" msgstr "" -#: part/views.py:223 +#: part/views.py:227 msgid "Set Part Category" msgstr "" -#: part/views.py:273 +#: part/views.py:277 #, python-brace-format msgid "Set category for {n} parts" msgstr "" -#: part/views.py:308 +#: part/views.py:312 msgid "Create Variant" msgstr "" -#: part/views.py:388 +#: part/views.py:394 msgid "Duplicate Part" msgstr "" -#: part/views.py:395 +#: part/views.py:401 msgid "Copied part" msgstr "" -#: part/views.py:514 templates/js/stock.js:793 +#: part/views.py:455 part/views.py:585 +msgid "Possible matches exist - confirm creation of new part" +msgstr "" + +#: part/views.py:520 templates/js/stock.js:840 msgid "Create New Part" msgstr "" -#: part/views.py:521 +#: part/views.py:527 msgid "Created new part" msgstr "" -#: part/views.py:736 +#: part/views.py:743 msgid "Part QR Code" msgstr "" -#: part/views.py:755 +#: part/views.py:762 msgid "Upload Part Image" msgstr "" -#: part/views.py:763 part/views.py:800 +#: part/views.py:770 part/views.py:807 msgid "Updated part image" msgstr "" -#: part/views.py:772 +#: part/views.py:779 msgid "Select Part Image" msgstr "" -#: part/views.py:803 +#: part/views.py:810 msgid "Part image not found" msgstr "" -#: part/views.py:814 +#: part/views.py:821 msgid "Edit Part Properties" msgstr "" -#: part/views.py:841 +#: part/views.py:848 msgid "Duplicate BOM" msgstr "" -#: part/views.py:872 +#: part/views.py:879 msgid "Confirm duplication of BOM from parent" msgstr "" -#: part/views.py:890 +#: part/views.py:900 msgid "Validate BOM" msgstr "" -#: part/views.py:1057 +#: part/views.py:923 +msgid "Confirm that the BOM is valid" +msgstr "" + +#: part/views.py:934 +msgid "Validated Bill of Materials" +msgstr "" + +#: part/views.py:1068 msgid "No BOM file provided" msgstr "" -#: part/views.py:1407 +#: part/views.py:1418 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1432 part/views.py:1435 +#: part/views.py:1443 part/views.py:1446 msgid "Select valid part" msgstr "" -#: part/views.py:1441 +#: part/views.py:1452 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1479 +#: part/views.py:1490 msgid "Select a part" msgstr "" -#: part/views.py:1485 +#: part/views.py:1496 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1489 +#: part/views.py:1500 msgid "Specify quantity" msgstr "" -#: part/views.py:1745 +#: part/views.py:1756 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1754 +#: part/views.py:1765 msgid "Part was deleted" msgstr "" -#: part/views.py:1763 +#: part/views.py:1774 msgid "Part Pricing" msgstr "" -#: part/views.py:1889 +#: part/views.py:1900 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1899 +#: part/views.py:1910 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1908 +#: part/views.py:1919 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1918 +#: part/views.py:1929 msgid "Create Part Parameter" msgstr "" -#: part/views.py:1970 +#: part/views.py:1981 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:1986 +#: part/views.py:1997 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:2045 +#: part/views.py:2056 msgid "Edit Part Category" msgstr "" -#: part/views.py:2082 +#: part/views.py:2093 msgid "Delete Part Category" msgstr "" -#: part/views.py:2090 +#: part/views.py:2101 msgid "Part category was deleted" msgstr "" -#: part/views.py:2153 +#: part/views.py:2164 msgid "Create BOM Item" msgstr "" -#: part/views.py:2221 +#: part/views.py:2232 msgid "Edit BOM item" msgstr "" -#: part/views.py:2271 +#: part/views.py:2282 msgid "Confim BOM item deletion" msgstr "" @@ -3222,6 +3429,10 @@ msgstr "" msgid "Asset file description" msgstr "" +#: stock/forms.py:111 +msgid "Enter unique serial numbers (or leave blank)" +msgstr "" + #: stock/forms.py:191 msgid "Label" msgstr "" @@ -3262,10 +3473,6 @@ msgstr "" msgid "Confirm removal of installed stock items" msgstr "" -#: stock/forms.py:364 -msgid "Destination" -msgstr "" - #: stock/forms.py:364 msgid "Destination stock location" msgstr "" @@ -3274,7 +3481,7 @@ msgstr "" msgid "Add note (required)" msgstr "" -#: stock/forms.py:370 stock/views.py:921 stock/views.py:1119 +#: stock/forms.py:370 stock/views.py:916 stock/views.py:1114 msgid "Confirm stock adjustment" msgstr "" @@ -3290,227 +3497,227 @@ msgstr "" msgid "Set the destination as the default location for selected parts" msgstr "" -#: stock/models.py:212 +#: stock/models.py:178 +msgid "Created stock item" +msgstr "" + +#: stock/models.py:214 msgid "StockItem with this serial number already exists" msgstr "" -#: stock/models.py:248 +#: stock/models.py:250 #, python-brace-format msgid "Part type ('{pf}') must be {pe}" msgstr "" -#: stock/models.py:258 stock/models.py:267 +#: stock/models.py:260 stock/models.py:269 msgid "Quantity must be 1 for item with a serial number" msgstr "" -#: stock/models.py:259 +#: stock/models.py:261 msgid "Serial number cannot be set if quantity greater than 1" msgstr "" -#: stock/models.py:281 +#: stock/models.py:283 msgid "Item cannot belong to itself" msgstr "" -#: stock/models.py:287 +#: stock/models.py:289 msgid "Item must have a build reference if is_building=True" msgstr "" -#: stock/models.py:294 +#: stock/models.py:296 msgid "Build reference does not point to the same part object" msgstr "" -#: stock/models.py:327 +#: stock/models.py:329 msgid "Parent Stock Item" msgstr "" -#: stock/models.py:336 +#: stock/models.py:338 msgid "Base part" msgstr "" -#: stock/models.py:345 +#: stock/models.py:347 msgid "Select a matching supplier part for this stock item" msgstr "" -#: stock/models.py:350 stock/templates/stock/stock_app_base.html:7 +#: stock/models.py:352 stock/templates/stock/stock_app_base.html:7 msgid "Stock Location" msgstr "" -#: stock/models.py:353 +#: stock/models.py:355 msgid "Where is this stock item located?" msgstr "" -#: stock/models.py:358 stock/templates/stock/item_base.html:199 +#: stock/models.py:360 stock/templates/stock/item_base.html:212 msgid "Installed In" msgstr "" -#: stock/models.py:361 +#: stock/models.py:363 msgid "Is this item installed in another item?" msgstr "" -#: stock/models.py:377 +#: stock/models.py:379 msgid "Serial number for this item" msgstr "" -#: stock/models.py:389 +#: stock/models.py:391 msgid "Batch code for this stock item" msgstr "" -#: stock/models.py:393 +#: stock/models.py:395 msgid "Stock Quantity" msgstr "" -#: stock/models.py:402 +#: stock/models.py:404 msgid "Source Build" msgstr "" -#: stock/models.py:404 +#: stock/models.py:406 msgid "Build for this stock item" msgstr "" -#: stock/models.py:415 +#: stock/models.py:417 msgid "Source Purchase Order" msgstr "" -#: stock/models.py:418 +#: stock/models.py:420 msgid "Purchase order for this stock item" msgstr "" -#: stock/models.py:424 +#: stock/models.py:426 msgid "Destination Sales Order" msgstr "" -#: stock/models.py:431 +#: stock/models.py:433 msgid "Destination Build Order" msgstr "" -#: stock/models.py:444 +#: stock/models.py:446 msgid "Delete this Stock Item when stock is depleted" msgstr "" -#: stock/models.py:454 stock/templates/stock/item_notes.html:14 +#: stock/models.py:456 stock/templates/stock/item_notes.html:14 #: stock/templates/stock/item_notes.html:30 msgid "Stock Item Notes" msgstr "" -#: stock/models.py:505 +#: stock/models.py:507 msgid "Assigned to Customer" msgstr "" -#: stock/models.py:507 +#: stock/models.py:509 msgid "Manually assigned to customer" msgstr "" -#: stock/models.py:520 +#: stock/models.py:522 msgid "Returned from customer" msgstr "" -#: stock/models.py:522 +#: stock/models.py:524 msgid "Returned to location" msgstr "" -#: stock/models.py:650 +#: stock/models.py:652 msgid "Installed into stock item" msgstr "" -#: stock/models.py:658 +#: stock/models.py:660 msgid "Installed stock item" msgstr "" -#: stock/models.py:682 +#: stock/models.py:684 msgid "Uninstalled stock item" msgstr "" -#: stock/models.py:701 +#: stock/models.py:703 msgid "Uninstalled into location" msgstr "" -#: stock/models.py:796 +#: stock/models.py:807 msgid "Part is not set as trackable" msgstr "" -#: stock/models.py:802 +#: stock/models.py:813 msgid "Quantity must be integer" msgstr "" -#: stock/models.py:808 +#: stock/models.py:819 #, python-brace-format msgid "Quantity must not exceed available stock quantity ({n})" msgstr "" -#: stock/models.py:811 +#: stock/models.py:822 msgid "Serial numbers must be a list of integers" msgstr "" -#: stock/models.py:814 +#: stock/models.py:825 msgid "Quantity does not match serial numbers" msgstr "" -#: stock/models.py:824 -msgid "Serial numbers already exist: " -msgstr "" - -#: stock/models.py:849 +#: stock/models.py:857 msgid "Add serial number" msgstr "" -#: stock/models.py:852 +#: stock/models.py:860 #, python-brace-format msgid "Serialized {n} items" msgstr "" -#: stock/models.py:963 +#: stock/models.py:971 msgid "StockItem cannot be moved as it is not in stock" msgstr "" -#: stock/models.py:1292 +#: stock/models.py:1316 msgid "Tracking entry title" msgstr "" -#: stock/models.py:1294 +#: stock/models.py:1318 msgid "Entry notes" msgstr "" -#: stock/models.py:1296 +#: stock/models.py:1320 msgid "Link to external page for further information" msgstr "" -#: stock/models.py:1356 +#: stock/models.py:1380 msgid "Value must be provided for this test" msgstr "" -#: stock/models.py:1362 +#: stock/models.py:1386 msgid "Attachment must be uploaded for this test" msgstr "" -#: stock/models.py:1379 +#: stock/models.py:1403 msgid "Test" msgstr "" -#: stock/models.py:1380 +#: stock/models.py:1404 msgid "Test name" msgstr "" -#: stock/models.py:1385 +#: stock/models.py:1409 msgid "Result" msgstr "" -#: stock/models.py:1386 templates/js/table_filters.js:157 +#: stock/models.py:1410 templates/js/table_filters.js:162 msgid "Test result" msgstr "" -#: stock/models.py:1392 +#: stock/models.py:1416 msgid "Test output value" msgstr "" -#: stock/models.py:1398 +#: stock/models.py:1422 msgid "Attachment" msgstr "" -#: stock/models.py:1399 +#: stock/models.py:1423 msgid "Test result attachment" msgstr "" -#: stock/models.py:1405 +#: stock/models.py:1429 msgid "Test notes" msgstr "" @@ -3527,132 +3734,145 @@ msgid "Stock Item Attachments" msgstr "" #: stock/templates/stock/item_base.html:20 +msgid "This stock item is in production and cannot be edited." +msgstr "" + +#: stock/templates/stock/item_base.html:21 +msgid "Edit the stock item from the build view." +msgstr "" + +#: stock/templates/stock/item_base.html:34 msgid "This stock item has not passed all required tests" msgstr "" -#: stock/templates/stock/item_base.html:26 +#: stock/templates/stock/item_base.html:40 msgid "This stock item is allocated to Sales Order" msgstr "" -#: stock/templates/stock/item_base.html:32 +#: stock/templates/stock/item_base.html:46 msgid "This stock item is allocated to Build" msgstr "" -#: stock/templates/stock/item_base.html:38 +#: stock/templates/stock/item_base.html:52 msgid "" "This stock item is serialized - it has a unique serial number and the " "quantity cannot be adjusted." msgstr "" -#: stock/templates/stock/item_base.html:42 +#: stock/templates/stock/item_base.html:56 msgid "This stock item cannot be deleted as it has child items" msgstr "" -#: stock/templates/stock/item_base.html:46 +#: stock/templates/stock/item_base.html:60 msgid "" "This stock item will be automatically deleted when all stock is depleted." msgstr "" -#: stock/templates/stock/item_base.html:94 templates/js/barcode.js:283 +#: stock/templates/stock/item_base.html:107 templates/js/barcode.js:283 #: templates/js/barcode.js:288 msgid "Unlink Barcode" msgstr "" -#: stock/templates/stock/item_base.html:96 +#: stock/templates/stock/item_base.html:109 msgid "Link Barcode" msgstr "" -#: stock/templates/stock/item_base.html:104 +#: stock/templates/stock/item_base.html:117 msgid "Stock adjustment actions" msgstr "" -#: stock/templates/stock/item_base.html:108 +#: stock/templates/stock/item_base.html:121 #: stock/templates/stock/location.html:41 templates/stock_table.html:23 msgid "Count stock" msgstr "" -#: stock/templates/stock/item_base.html:109 templates/stock_table.html:21 +#: stock/templates/stock/item_base.html:122 templates/stock_table.html:21 msgid "Add stock" msgstr "" -#: stock/templates/stock/item_base.html:110 templates/stock_table.html:22 +#: stock/templates/stock/item_base.html:123 templates/stock_table.html:22 msgid "Remove stock" msgstr "" -#: stock/templates/stock/item_base.html:112 +#: stock/templates/stock/item_base.html:125 msgid "Transfer stock" msgstr "" -#: stock/templates/stock/item_base.html:114 +#: stock/templates/stock/item_base.html:127 msgid "Serialize stock" msgstr "" -#: stock/templates/stock/item_base.html:118 +#: stock/templates/stock/item_base.html:131 msgid "Assign to customer" msgstr "" -#: stock/templates/stock/item_base.html:121 +#: stock/templates/stock/item_base.html:134 msgid "Return to stock" msgstr "" -#: stock/templates/stock/item_base.html:125 templates/js/stock.js:934 +#: stock/templates/stock/item_base.html:138 templates/js/stock.js:981 msgid "Uninstall stock item" msgstr "" -#: stock/templates/stock/item_base.html:125 +#: stock/templates/stock/item_base.html:138 msgid "Uninstall" msgstr "" -#: stock/templates/stock/item_base.html:134 +#: stock/templates/stock/item_base.html:147 #: stock/templates/stock/location.html:38 msgid "Stock actions" msgstr "" -#: stock/templates/stock/item_base.html:137 +#: stock/templates/stock/item_base.html:150 msgid "Convert to variant" msgstr "" -#: stock/templates/stock/item_base.html:140 +#: stock/templates/stock/item_base.html:153 msgid "Duplicate stock item" msgstr "" -#: stock/templates/stock/item_base.html:142 +#: stock/templates/stock/item_base.html:155 msgid "Edit stock item" msgstr "" -#: stock/templates/stock/item_base.html:145 +#: stock/templates/stock/item_base.html:158 msgid "Delete stock item" msgstr "" -#: stock/templates/stock/item_base.html:151 +#: stock/templates/stock/item_base.html:164 msgid "Generate test report" msgstr "" -#: stock/templates/stock/item_base.html:159 +#: stock/templates/stock/item_base.html:172 msgid "Stock Item Details" msgstr "" -#: stock/templates/stock/item_base.html:224 +#: stock/templates/stock/item_base.html:237 templates/js/build.js:426 msgid "No location set" msgstr "" -#: stock/templates/stock/item_base.html:231 +#: stock/templates/stock/item_base.html:244 msgid "Barcode Identifier" msgstr "" -#: stock/templates/stock/item_base.html:259 +#: stock/templates/stock/item_base.html:258 templates/js/build.js:626 +#: templates/navbar.html:25 +msgid "Build" +msgstr "" + +#: stock/templates/stock/item_base.html:272 msgid "Parent Item" msgstr "" -#: stock/templates/stock/item_base.html:284 +#: stock/templates/stock/item_base.html:297 msgid "Last Updated" msgstr "" -#: stock/templates/stock/item_base.html:289 +#: stock/templates/stock/item_base.html:302 msgid "Last Stocktake" msgstr "" -#: stock/templates/stock/item_base.html:293 +#: stock/templates/stock/item_base.html:306 msgid "No stocktake performed" msgstr "" @@ -3772,7 +3992,7 @@ msgstr "" msgid "The following stock items will be uninstalled" msgstr "" -#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1315 +#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1310 msgid "Convert Stock Item" msgstr "" @@ -3816,202 +4036,210 @@ msgstr "" msgid "Add Stock Item Attachment" msgstr "" -#: stock/views.py:209 +#: stock/views.py:210 msgid "Edit Stock Item Attachment" msgstr "" -#: stock/views.py:226 +#: stock/views.py:227 msgid "Delete Stock Item Attachment" msgstr "" -#: stock/views.py:243 +#: stock/views.py:244 msgid "Assign to Customer" msgstr "" -#: stock/views.py:281 +#: stock/views.py:254 +msgid "Customer must be specified" +msgstr "" + +#: stock/views.py:278 msgid "Return to Stock" msgstr "" -#: stock/views.py:301 +#: stock/views.py:288 msgid "Specify a valid location" msgstr "" -#: stock/views.py:305 +#: stock/views.py:299 msgid "Stock item returned from customer" msgstr "" -#: stock/views.py:317 +#: stock/views.py:309 msgid "Select Label Template" msgstr "" -#: stock/views.py:340 +#: stock/views.py:332 msgid "Select valid label" msgstr "" -#: stock/views.py:404 +#: stock/views.py:396 msgid "Delete All Test Data" msgstr "" -#: stock/views.py:420 +#: stock/views.py:412 msgid "Confirm test data deletion" msgstr "" -#: stock/views.py:440 +#: stock/views.py:432 msgid "Add Test Result" msgstr "" -#: stock/views.py:478 +#: stock/views.py:473 msgid "Edit Test Result" msgstr "" -#: stock/views.py:496 +#: stock/views.py:491 msgid "Delete Test Result" msgstr "" -#: stock/views.py:508 +#: stock/views.py:503 msgid "Select Test Report Template" msgstr "" -#: stock/views.py:523 +#: stock/views.py:518 msgid "Select valid template" msgstr "" -#: stock/views.py:576 +#: stock/views.py:571 msgid "Stock Export Options" msgstr "" -#: stock/views.py:698 +#: stock/views.py:693 msgid "Stock Item QR Code" msgstr "" -#: stock/views.py:724 +#: stock/views.py:719 msgid "Install Stock Item" msgstr "" -#: stock/views.py:824 +#: stock/views.py:819 msgid "Uninstall Stock Items" msgstr "" -#: stock/views.py:932 +#: stock/views.py:927 msgid "Uninstalled stock items" msgstr "" -#: stock/views.py:957 +#: stock/views.py:952 msgid "Adjust Stock" msgstr "" -#: stock/views.py:1067 +#: stock/views.py:1062 msgid "Move Stock Items" msgstr "" -#: stock/views.py:1068 +#: stock/views.py:1063 msgid "Count Stock Items" msgstr "" -#: stock/views.py:1069 +#: stock/views.py:1064 msgid "Remove From Stock" msgstr "" -#: stock/views.py:1070 +#: stock/views.py:1065 msgid "Add Stock Items" msgstr "" -#: stock/views.py:1071 +#: stock/views.py:1066 msgid "Delete Stock Items" msgstr "" -#: stock/views.py:1099 +#: stock/views.py:1094 msgid "Must enter integer value" msgstr "" -#: stock/views.py:1104 +#: stock/views.py:1099 msgid "Quantity must be positive" msgstr "" -#: stock/views.py:1111 +#: stock/views.py:1106 #, python-brace-format msgid "Quantity must not exceed {x}" msgstr "" -#: stock/views.py:1190 +#: stock/views.py:1185 #, python-brace-format msgid "Added stock to {n} items" msgstr "" -#: stock/views.py:1205 +#: stock/views.py:1200 #, python-brace-format msgid "Removed stock from {n} items" msgstr "" -#: stock/views.py:1218 +#: stock/views.py:1213 #, python-brace-format msgid "Counted stock for {n} items" msgstr "" -#: stock/views.py:1246 +#: stock/views.py:1241 msgid "No items were moved" msgstr "" -#: stock/views.py:1249 +#: stock/views.py:1244 #, python-brace-format msgid "Moved {n} items to {dest}" msgstr "" -#: stock/views.py:1268 +#: stock/views.py:1263 #, python-brace-format msgid "Deleted {n} stock items" msgstr "" -#: stock/views.py:1280 +#: stock/views.py:1275 msgid "Edit Stock Item" msgstr "" -#: stock/views.py:1365 +#: stock/views.py:1360 msgid "Serialize Stock" msgstr "" -#: stock/views.py:1559 +#: stock/views.py:1454 templates/js/build.js:210 +msgid "Create new Stock Item" +msgstr "" + +#: stock/views.py:1553 msgid "Duplicate Stock Item" msgstr "" -#: stock/views.py:1625 +#: stock/views.py:1619 msgid "Invalid quantity" msgstr "" -#: stock/views.py:1628 +#: stock/views.py:1622 msgid "Quantity cannot be less than zero" msgstr "" -#: stock/views.py:1632 +#: stock/views.py:1626 msgid "Invalid part selection" msgstr "" -#: stock/views.py:1681 +#: stock/views.py:1674 #, python-brace-format msgid "Created {n} new stock items" msgstr "" -#: stock/views.py:1700 stock/views.py:1716 +#: stock/views.py:1693 stock/views.py:1709 msgid "Created new stock item" msgstr "" -#: stock/views.py:1735 +#: stock/views.py:1728 msgid "Delete Stock Location" msgstr "" -#: stock/views.py:1749 +#: stock/views.py:1742 msgid "Delete Stock Item" msgstr "" -#: stock/views.py:1761 +#: stock/views.py:1754 msgid "Delete Stock Tracking Entry" msgstr "" -#: stock/views.py:1780 +#: stock/views.py:1773 msgid "Edit Stock Tracking Entry" msgstr "" -#: stock/views.py:1790 +#: stock/views.py:1783 msgid "Add Stock Tracking Entry" msgstr "" @@ -4067,11 +4295,11 @@ msgstr "" msgid "Enter a search query" msgstr "" -#: templates/InvenTree/search.html:191 templates/js/stock.js:528 +#: templates/InvenTree/search.html:191 templates/js/stock.js:289 msgid "Shipped to customer" msgstr "" -#: templates/InvenTree/search.html:194 templates/js/stock.js:538 +#: templates/InvenTree/search.html:194 templates/js/stock.js:299 msgid "No stock location set" msgstr "" @@ -4191,18 +4419,10 @@ msgstr "" msgid "User Information" msgstr "" -#: templates/InvenTree/settings/user.html:18 -msgid "Edit User Information" -msgstr "" - #: templates/InvenTree/settings/user.html:21 msgid "Change Password" msgstr "" -#: templates/InvenTree/settings/user.html:22 -msgid "Set Password" -msgstr "" - #: templates/InvenTree/settings/user.html:28 msgid "Username" msgstr "" @@ -4360,72 +4580,99 @@ msgstr "" msgid "Barcode does not match Stock Item" msgstr "" -#: templates/js/bom.js:156 templates/js/part.js:117 templates/js/part.js:344 -msgid "Trackable part" -msgstr "" - -#: templates/js/bom.js:160 templates/js/part.js:121 templates/js/part.js:348 -msgid "Virtual part" -msgstr "" - -#: templates/js/bom.js:164 templates/js/company.js:147 templates/js/part.js:125 -#: templates/js/part.js:353 -msgid "Template part" -msgstr "" - -#: templates/js/bom.js:169 +#: templates/js/bom.js:159 msgid "Open subassembly" msgstr "" -#: templates/js/bom.js:214 +#: templates/js/bom.js:200 msgid "Optional" msgstr "" -#: templates/js/bom.js:229 templates/js/build.js:133 -msgid "Available" -msgstr "" - -#: templates/js/bom.js:254 +#: templates/js/bom.js:240 msgid "No pricing available" msgstr "" -#: templates/js/bom.js:273 +#: templates/js/bom.js:259 templates/js/build.js:555 msgid "Actions" msgstr "" -#: templates/js/bom.js:281 +#: templates/js/bom.js:267 msgid "Validate BOM Item" msgstr "" -#: templates/js/bom.js:283 +#: templates/js/bom.js:269 msgid "This line has been validated" msgstr "" -#: templates/js/bom.js:285 +#: templates/js/bom.js:271 msgid "Edit BOM Item" msgstr "" -#: templates/js/bom.js:287 +#: templates/js/bom.js:273 msgid "Delete BOM Item" msgstr "" -#: templates/js/bom.js:507 +#: templates/js/bom.js:346 templates/js/build.js:289 +msgid "No BOM items found" +msgstr "" + +#: templates/js/bom.js:491 msgid "INACTIVE" msgstr "" -#: templates/js/bom.js:521 +#: templates/js/bom.js:505 msgid "Uses" msgstr "" -#: templates/js/bom.js:532 +#: templates/js/bom.js:516 msgid "No matching parts found" msgstr "" -#: templates/js/build.js:24 +#: templates/js/build.js:56 +msgid "Auto-allocate stock items to this output" +msgstr "" + +#: templates/js/build.js:62 +msgid "Complete build output" +msgstr "" + +#: templates/js/build.js:71 +msgid "Unallocate stock from build output" +msgstr "" + +#: templates/js/build.js:77 +msgid "Delete build output" +msgstr "" + +#: templates/js/build.js:209 templates/stock_table.html:13 +msgid "New Stock Item" +msgstr "" + +#: templates/js/build.js:477 +msgid "Required Part" +msgstr "" + +#: templates/js/build.js:498 +msgid "Quantity Per" +msgstr "" + +#: templates/js/build.js:562 +msgid "Build stock" +msgstr "" + +#: templates/js/build.js:566 templates/stock_table.html:25 +msgid "Order stock" +msgstr "" + +#: templates/js/build.js:569 +msgid "Allocate stock" +msgstr "" + +#: templates/js/build.js:610 msgid "No builds matching query" msgstr "" -#: templates/js/build.js:122 +#: templates/js/build.js:720 msgid "No parts allocated for" msgstr "" @@ -4445,8 +4692,11 @@ msgstr "" msgid "No supplier parts found" msgstr "" -#: templates/js/company.js:151 templates/js/part.js:129 -#: templates/js/part.js:357 +#: templates/js/company.js:147 templates/js/part.js:79 templates/js/part.js:164 +msgid "Template part" +msgstr "" + +#: templates/js/company.js:151 templates/js/part.js:83 templates/js/part.js:168 msgid "Assembled part" msgstr "" @@ -4458,7 +4708,7 @@ msgstr "" msgid "No purchase orders found" msgstr "" -#: templates/js/order.js:180 templates/js/stock.js:643 +#: templates/js/order.js:180 templates/js/stock.js:677 msgid "Date" msgstr "" @@ -4470,59 +4720,68 @@ msgstr "" msgid "Shipment Date" msgstr "" -#: templates/js/part.js:166 -msgid "No variants found" +#: templates/js/part.js:71 templates/js/part.js:156 +msgid "Trackable part" msgstr "" -#: templates/js/part.js:252 templates/js/part.js:450 -msgid "No parts found" +#: templates/js/part.js:75 templates/js/part.js:160 +msgid "Virtual part" msgstr "" -#: templates/js/part.js:304 templates/js/stock.js:409 templates/js/stock.js:966 -msgid "Select" -msgstr "" - -#: templates/js/part.js:361 +#: templates/js/part.js:87 msgid "Starred part" msgstr "" -#: templates/js/part.js:365 +#: templates/js/part.js:91 msgid "Salable part" msgstr "" -#: templates/js/part.js:404 +#: templates/js/part.js:205 +msgid "No variants found" +msgstr "" + +#: templates/js/part.js:291 templates/js/part.js:457 +msgid "No parts found" +msgstr "" + +#: templates/js/part.js:343 templates/js/stock.js:456 +#: templates/js/stock.js:1013 +msgid "Select" +msgstr "" + +#: templates/js/part.js:411 msgid "No category" msgstr "" -#: templates/js/part.js:422 templates/js/table_filters.js:251 +#: templates/js/part.js:429 templates/js/table_filters.js:256 msgid "Low stock" msgstr "" -#: templates/js/part.js:431 +#: templates/js/part.js:438 msgid "Building" msgstr "" -#: templates/js/part.js:510 +#: templates/js/part.js:517 msgid "YES" msgstr "" -#: templates/js/part.js:512 +#: templates/js/part.js:519 msgid "NO" msgstr "" -#: templates/js/part.js:546 +#: templates/js/part.js:553 msgid "No test templates matching query" msgstr "" -#: templates/js/part.js:597 templates/js/stock.js:63 +#: templates/js/part.js:604 templates/js/stock.js:63 msgid "Edit test result" msgstr "" -#: templates/js/part.js:598 templates/js/stock.js:64 +#: templates/js/part.js:605 templates/js/stock.js:64 msgid "Delete test result" msgstr "" -#: templates/js/part.js:604 +#: templates/js/part.js:611 msgid "This test is defined for a parent part" msgstr "" @@ -4550,71 +4809,75 @@ msgstr "" msgid "Test Date" msgstr "" -#: templates/js/stock.js:263 +#: templates/js/stock.js:281 +msgid "In production" +msgstr "" + +#: templates/js/stock.js:285 +msgid "Installed in Stock Item" +msgstr "" + +#: templates/js/stock.js:293 +msgid "Assigned to Sales Order" +msgstr "" + +#: templates/js/stock.js:313 msgid "No stock items matching query" msgstr "" -#: templates/js/stock.js:361 templates/js/stock.js:376 +#: templates/js/stock.js:424 msgid "Undefined location" msgstr "" -#: templates/js/stock.js:469 -msgid "Stock item has been allocated" -msgstr "" - -#: templates/js/stock.js:473 -msgid "Stock item has been assigned to customer" -msgstr "" - -#: templates/js/stock.js:476 -msgid "Stock item was assigned to a build order" -msgstr "" - -#: templates/js/stock.js:478 -msgid "Stock item was assigned to a sales order" -msgstr "" - -#: templates/js/stock.js:483 -msgid "Stock item has been installed in another item" -msgstr "" - -#: templates/js/stock.js:490 -msgid "Stock item has been rejected" -msgstr "" - -#: templates/js/stock.js:494 -msgid "Stock item is lost" -msgstr "" - -#: templates/js/stock.js:498 templates/js/table_filters.js:106 -msgid "Depleted" +#: templates/js/stock.js:518 +msgid "Stock item is in production" msgstr "" #: templates/js/stock.js:523 -msgid "Installed in Stock Item " +msgid "Stock item assigned to sales order" msgstr "" -#: templates/js/stock.js:531 -msgid "Assigned to sales order" +#: templates/js/stock.js:526 +msgid "Stock item assigned to customer" msgstr "" -#: templates/js/stock.js:709 +#: templates/js/stock.js:530 +msgid "Stock item has been allocated" +msgstr "" + +#: templates/js/stock.js:534 +msgid "Stock item has been installed in another item" +msgstr "" + +#: templates/js/stock.js:541 +msgid "Stock item has been rejected" +msgstr "" + +#: templates/js/stock.js:545 +msgid "Stock item is lost" +msgstr "" + +#: templates/js/stock.js:549 templates/js/table_filters.js:106 +msgid "Depleted" +msgstr "" + +#: templates/js/stock.js:743 msgid "No user information" msgstr "" -#: templates/js/stock.js:805 +#: templates/js/stock.js:852 msgid "Create New Location" msgstr "" -#: templates/js/stock.js:904 +#: templates/js/stock.js:951 msgid "Serial" msgstr "" -#: templates/js/stock.js:997 templates/js/table_filters.js:116 +#: templates/js/stock.js:1044 templates/js/table_filters.js:121 msgid "Installed" msgstr "" -#: templates/js/stock.js:1022 +#: templates/js/stock.js:1069 msgid "Install item" msgstr "" @@ -4626,36 +4889,36 @@ msgstr "" msgid "Validated" msgstr "" -#: templates/js/table_filters.js:65 templates/js/table_filters.js:126 +#: templates/js/table_filters.js:65 templates/js/table_filters.js:131 msgid "Is Serialized" msgstr "" -#: templates/js/table_filters.js:68 templates/js/table_filters.js:133 +#: templates/js/table_filters.js:68 templates/js/table_filters.js:138 msgid "Serial number GTE" msgstr "" -#: templates/js/table_filters.js:69 templates/js/table_filters.js:134 +#: templates/js/table_filters.js:69 templates/js/table_filters.js:139 msgid "Serial number greater than or equal to" msgstr "" -#: templates/js/table_filters.js:72 templates/js/table_filters.js:137 +#: templates/js/table_filters.js:72 templates/js/table_filters.js:142 msgid "Serial number LTE" msgstr "" -#: templates/js/table_filters.js:73 templates/js/table_filters.js:138 +#: templates/js/table_filters.js:73 templates/js/table_filters.js:143 msgid "Serial number less than or equal to" msgstr "" #: templates/js/table_filters.js:76 templates/js/table_filters.js:77 -#: templates/js/table_filters.js:129 templates/js/table_filters.js:130 +#: templates/js/table_filters.js:134 templates/js/table_filters.js:135 msgid "Serial number" msgstr "" -#: templates/js/table_filters.js:81 templates/js/table_filters.js:147 +#: templates/js/table_filters.js:81 templates/js/table_filters.js:152 msgid "Batch code" msgstr "" -#: templates/js/table_filters.js:91 templates/js/table_filters.js:218 +#: templates/js/table_filters.js:91 templates/js/table_filters.js:223 msgid "Active parts" msgstr "" @@ -4687,66 +4950,86 @@ msgstr "" msgid "Show items which are in stock" msgstr "" -#: templates/js/table_filters.js:117 -msgid "Show stock items which are installed in another item" +#: templates/js/table_filters.js:116 +msgid "In Production" msgstr "" -#: templates/js/table_filters.js:121 -msgid "Sent to customer" +#: templates/js/table_filters.js:117 +msgid "Show items which are in production" msgstr "" #: templates/js/table_filters.js:122 +msgid "Show stock items which are installed in another item" +msgstr "" + +#: templates/js/table_filters.js:126 +msgid "Sent to customer" +msgstr "" + +#: templates/js/table_filters.js:127 msgid "Show items which have been assigned to a customer" msgstr "" -#: templates/js/table_filters.js:142 templates/js/table_filters.js:143 +#: templates/js/table_filters.js:147 templates/js/table_filters.js:148 msgid "Stock status" msgstr "" -#: templates/js/table_filters.js:176 +#: templates/js/table_filters.js:181 msgid "Build status" msgstr "" -#: templates/js/table_filters.js:191 templates/js/table_filters.js:204 +#: templates/js/table_filters.js:196 templates/js/table_filters.js:209 msgid "Order status" msgstr "" -#: templates/js/table_filters.js:196 templates/js/table_filters.js:209 +#: templates/js/table_filters.js:201 templates/js/table_filters.js:214 msgid "Outstanding" msgstr "" -#: templates/js/table_filters.js:228 +#: templates/js/table_filters.js:233 msgid "Include subcategories" msgstr "" -#: templates/js/table_filters.js:229 +#: templates/js/table_filters.js:234 msgid "Include parts in subcategories" msgstr "" -#: templates/js/table_filters.js:233 +#: templates/js/table_filters.js:238 msgid "Has IPN" msgstr "" -#: templates/js/table_filters.js:234 +#: templates/js/table_filters.js:239 msgid "Part has internal part number" msgstr "" -#: templates/js/table_filters.js:239 +#: templates/js/table_filters.js:244 msgid "Show active parts" msgstr "" -#: templates/js/table_filters.js:247 +#: templates/js/table_filters.js:252 msgid "Stock available" msgstr "" -#: templates/js/table_filters.js:263 +#: templates/js/table_filters.js:268 msgid "Starred" msgstr "" -#: templates/js/table_filters.js:275 +#: templates/js/table_filters.js:280 msgid "Purchasable" msgstr "" +#: templates/modals.html:13 templates/modals.html:35 +msgid "Form errors exist" +msgstr "" + +#: templates/modals.html:18 templates/modals.html:40 +msgid "Close" +msgstr "" + +#: templates/modals.html:19 templates/modals.html:41 +msgid "Submit" +msgstr "" + #: templates/navbar.html:29 msgid "Buy" msgstr "" @@ -4811,10 +5094,6 @@ msgstr "" msgid "Order selected items" msgstr "" -#: templates/stock_table.html:25 -msgid "Order stock" -msgstr "" - #: templates/stock_table.html:28 msgid "Delete selected items" msgstr "" @@ -4843,38 +5122,38 @@ msgstr "" msgid "Important dates" msgstr "" -#: users/models.py:128 +#: users/models.py:129 msgid "Permission set" msgstr "" -#: users/models.py:136 +#: users/models.py:137 msgid "Group" msgstr "" -#: users/models.py:139 +#: users/models.py:140 msgid "View" msgstr "" -#: users/models.py:139 +#: users/models.py:140 msgid "Permission to view items" msgstr "" -#: users/models.py:141 +#: users/models.py:142 msgid "Add" msgstr "" -#: users/models.py:141 +#: users/models.py:142 msgid "Permission to add items" msgstr "" -#: users/models.py:143 +#: users/models.py:144 msgid "Change" msgstr "" -#: users/models.py:143 +#: users/models.py:144 msgid "Permissions to edit items" msgstr "" -#: users/models.py:145 +#: users/models.py:146 msgid "Permission to delete items" msgstr "" diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index e411afbdad..a65cc300fa 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -91,7 +91,7 @@ class SalesOrderDetail(InvenTreeRoleMixin, DetailView): class PurchaseOrderAttachmentCreate(AjaxCreateView): """ - View for creating a new PurchaseOrderAtt + View for creating a new PurchaseOrderAttachment """ model = PurchaseOrderAttachment @@ -100,7 +100,9 @@ class PurchaseOrderAttachmentCreate(AjaxCreateView): ajax_template_name = "modal_form.html" role_required = 'purchase_order.add' - def post_save(self, attachment, form, **kwargs): + def save(self, form, **kwargs): + + attachment = form.save(commit=False) attachment.user = self.request.user attachment.save() @@ -148,9 +150,14 @@ class SalesOrderAttachmentCreate(AjaxCreateView): ajax_form_title = _('Add Sales Order Attachment') role_required = 'sales_order.add' - def post_save(self, attachment, form, **kwargs): - self.object.user = self.request.user - self.object.save() + def save(self, form, **kwargs): + """ + Save the user that uploaded the attachment + """ + + attachment = form.save(commit=False) + attachment.user = self.request.user + attachment.save() def get_data(self): return { @@ -295,7 +302,9 @@ class SalesOrderNotes(InvenTreeRoleMixin, UpdateView): class PurchaseOrderCreate(AjaxCreateView): - """ View for creating a new PurchaseOrder object using a modal form """ + """ + View for creating a new PurchaseOrder object using a modal form + """ model = PurchaseOrder ajax_form_title = _("Create Purchase Order") @@ -319,9 +328,12 @@ class PurchaseOrderCreate(AjaxCreateView): return initials - def post_save(self, order, form, **kwargs): - # Record the user who created this purchase order + def save(self, form, **kwargs): + """ + Record the user who created this PurchaseOrder + """ + order = form.save(commit=False) order.created_by = self.request.user order.save() @@ -351,8 +363,12 @@ class SalesOrderCreate(AjaxCreateView): return initials - def post_save(self, order, form, **kwargs): - # Record the user who created this sales order + def save(self, form, **kwargs): + """ + Record the user who created this SalesOrder + """ + + order = form.save(commit=False) order.created_by = self.request.user order.save() @@ -414,7 +430,10 @@ class PurchaseOrderCancel(AjaxUpdateView): if not order.can_cancel(): form.add_error(None, _('Order cannot be cancelled')) - def post_save(self, order, form, **kwargs): + def save(self, order, form, **kwargs): + """ + Cancel the PurchaseOrder + """ order.cancel_order() @@ -438,7 +457,10 @@ class SalesOrderCancel(AjaxUpdateView): if not order.can_cancel(): form.add_error(None, _('Order cannot be cancelled')) - def post_save(self, order, form, **kwargs): + def save(self, order, form, **kwargs): + """ + Once the form has been validated, cancel the SalesOrder + """ order.cancel_order() @@ -459,8 +481,10 @@ class PurchaseOrderIssue(AjaxUpdateView): if not confirm: form.add_error('confirm', _('Confirm order placement')) - def post_save(self, order, form, **kwargs): - + def save(self, order, form, **kwargs): + """ + Once the form has been validated, place the order. + """ order.place_order() def get_data(self): @@ -495,7 +519,10 @@ class PurchaseOrderComplete(AjaxUpdateView): if not confirm: form.add_error('confirm', _('Confirm order completion')) - def post_save(self, order, form, **kwargs): + def save(self, order, form, **kwargs): + """ + Complete the PurchaseOrder + """ order.complete_order() @@ -883,9 +910,10 @@ class OrderParts(AjaxView): try: build = Build.objects.get(id=build_id) - parts = build.part.required_parts() + parts = build.required_parts for part in parts: + # If ordering from a Build page, ignore parts that we have enough of if part.quantity_to_order <= 0: continue diff --git a/InvenTree/part/admin.py b/InvenTree/part/admin.py index 61097d0952..02e6974aef 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -9,7 +9,7 @@ from import_export.fields import Field import import_export.widgets as widgets from .models import PartCategory, Part -from .models import PartAttachment, PartStar +from .models import PartAttachment, PartStar, PartRelated from .models import BomItem from .models import PartParameterTemplate, PartParameter from .models import PartCategoryParameterTemplate @@ -122,6 +122,11 @@ class PartCategoryAdmin(ImportExportModelAdmin): search_fields = ('name', 'description') +class PartRelatedAdmin(admin.ModelAdmin): + ''' Class to manage PartRelated objects ''' + pass + + class PartAttachmentAdmin(admin.ModelAdmin): list_display = ('part', 'attachment', 'comment') @@ -292,6 +297,7 @@ class PartSellPriceBreakAdmin(admin.ModelAdmin): admin.site.register(Part, PartAdmin) admin.site.register(PartCategory, PartCategoryAdmin) +admin.site.register(PartRelated, PartRelatedAdmin) admin.site.register(PartAttachment, PartAttachmentAdmin) admin.site.register(PartStar, PartStarAdmin) admin.site.register(BomItem, BomItemAdmin) diff --git a/InvenTree/part/apps.py b/InvenTree/part/apps.py index 198e58e337..2f72537136 100644 --- a/InvenTree/part/apps.py +++ b/InvenTree/part/apps.py @@ -16,8 +16,15 @@ class PartConfig(AppConfig): """ self.generate_part_thumbnails() + self.update_trackable_status() def generate_part_thumbnails(self): + """ + Generate thumbnail images for any Part that does not have one. + This function exists mainly for legacy support, + as any *new* image uploaded will have a thumbnail generated automatically. + """ + from .models import Part print("InvenTree: Checking Part image thumbnails") @@ -37,4 +44,27 @@ class PartConfig(AppConfig): part.image = None part.save() except (OperationalError, ProgrammingError): + # Exception if the database has not been migrated yet + pass + + def update_trackable_status(self): + """ + Check for any instances where a trackable part is used in the BOM + for a non-trackable part. + + In such a case, force the top-level part to be trackable too. + """ + + from .models import BomItem + + try: + items = BomItem.objects.filter(part__trackable=False, sub_part__trackable=True) + + for item in items: + print(f"Marking part '{item.part.name}' as trackable") + item.part.trackable = True + item.part.clean() + item.part.save() + except (OperationalError, ProgrammingError): + # Exception if the database has not been migrated yet pass diff --git a/InvenTree/part/fixtures/part.yaml b/InvenTree/part/fixtures/part.yaml index c77fd5dc57..9883edfcd3 100644 --- a/InvenTree/part/fixtures/part.yaml +++ b/InvenTree/part/fixtures/part.yaml @@ -67,6 +67,7 @@ name: 'Widget' description: 'A watchamacallit' category: 7 + assembly: true trackable: true tree_id: 0 level: 0 diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 94be763b2d..4e6413b34f 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -13,7 +13,7 @@ from mptt.fields import TreeNodeChoiceField from django import forms from django.utils.translation import ugettext as _ -from .models import Part, PartCategory, PartAttachment +from .models import Part, PartCategory, PartAttachment, PartRelated from .models import BomItem from .models import PartParameterTemplate, PartParameter from .models import PartCategoryParameterTemplate @@ -142,6 +142,25 @@ class BomUploadSelectFile(HelperForm): ] +class CreatePartRelatedForm(HelperForm): + """ Form for creating a PartRelated object """ + + class Meta: + model = PartRelated + fields = [ + 'part_1', + 'part_2', + ] + labels = { + 'part_2': _('Related Part'), + } + + def save(self): + """ Disable model saving """ + + return super(CreatePartRelatedForm, self).save(commit=False) + + class EditPartAttachmentForm(HelperForm): """ Form for editing a PartAttachment object """ diff --git a/InvenTree/part/migrations/0052_partrelated.py b/InvenTree/part/migrations/0052_partrelated.py new file mode 100644 index 0000000000..a8672ba7dc --- /dev/null +++ b/InvenTree/part/migrations/0052_partrelated.py @@ -0,0 +1,22 @@ +# Generated by Django 3.0.7 on 2020-10-16 20:42 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0051_bomitem_optional'), + ] + + operations = [ + migrations.CreateModel( + name='PartRelated', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('part_1', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='related_parts_1', to='part.Part')), + ('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')), + ], + ), + ] diff --git a/InvenTree/part/migrations/0053_merge_20201103_1028.py b/InvenTree/part/migrations/0053_merge_20201103_1028.py new file mode 100644 index 0000000000..d42595675a --- /dev/null +++ b/InvenTree/part/migrations/0053_merge_20201103_1028.py @@ -0,0 +1,14 @@ +# 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/models.py b/InvenTree/part/models.py index 55eef73018..705e4c6afd 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -564,10 +564,24 @@ class Part(MPTTModel): pass def clean(self): - """ Perform cleaning operations for the Part model """ + """ + Perform cleaning operations for the Part model + + Update trackable status: + If this part is trackable, and it is used in the BOM + for a parent part which is *not* trackable, + then we will force the parent part to be trackable. + """ super().clean() + if self.trackable: + for parent_part in self.used_in.all(): + if not parent_part.trackable: + parent_part.trackable = True + parent_part.clean() + parent_part.save() + name = models.CharField(max_length=100, blank=False, help_text=_('Part name'), validators=[validators.validate_part_name] @@ -909,6 +923,19 @@ class Part(MPTTModel): def has_bom(self): return self.bom_count > 0 + @property + def has_trackable_parts(self): + """ + Return True if any parts linked in the Bill of Materials are trackable. + This is important when building the part. + """ + + for bom_item in self.bom_items.all(): + if bom_item.sub_part.trackable: + return True + + return False + @property def bom_count(self): """ Return the number of items contained in the BOM for this part """ @@ -966,15 +993,31 @@ class Part(MPTTModel): self.bom_items.all().delete() - def required_parts(self): - """ Return a list of parts required to make this part (list of BOM items) """ - parts = [] - for bom in self.bom_items.all().select_related('sub_part'): - parts.append(bom.sub_part) + def getRequiredParts(self, recursive=False, parts=set()): + """ + Return a list of parts required to make this part (i.e. BOM items). + + Args: + recursive: If True iterate down through sub-assemblies + parts: Set of parts already found (to prevent recursion issues) + """ + + for bom_item in self.bom_items.all().select_related('sub_part'): + + sub_part = bom_item.sub_part + + if sub_part not in parts: + + parts.add(sub_part) + + if recursive: + sub_part.getRequiredParts(recursive=True, parts=parts) + return parts def get_allowed_bom_items(self): - """ Return a list of parts which can be added to a BOM for this part. + """ + Return a list of parts which can be added to a BOM for this part. - Exclude parts which are not 'component' parts - Exclude parts which this part is in the BOM for @@ -1176,7 +1219,7 @@ class Part(MPTTModel): parameter.save() @transaction.atomic - def deepCopy(self, other, **kwargs): + def deep_copy(self, other, **kwargs): """ Duplicates non-field data from another part. Does not alter the normal fields of this part, but can be used to copy other data linked by ForeignKey refernce. @@ -1331,6 +1374,32 @@ class Part(MPTTModel): return self.get_descendants(include_self=False) + def get_related_parts(self): + """ Return list of tuples for all related parts: + - first value is PartRelated object + - second value is matching Part object + """ + + related_parts = [] + + related_parts_1 = self.related_parts_1.filter(part_1__id=self.pk) + + related_parts_2 = self.related_parts_2.filter(part_2__id=self.pk) + + for related_part in related_parts_1: + # Add to related parts list + related_parts.append((related_part, related_part.part_2)) + + for related_part in related_parts_2: + # Add to related parts list + related_parts.append((related_part, related_part.part_1)) + + return related_parts + + @property + def related_count(self): + return len(self.get_related_parts()) + def attach_file(instance, filename): """ Function for storing a file for a PartAttachment @@ -1715,12 +1784,15 @@ class BomItem(models.Model): return self.get_item_hash() == self.checksum def clean(self): - """ Check validity of the BomItem model. + """ + Check validity of the BomItem model. Performs model checks beyond simple field validation. - A part cannot refer to itself in its BOM - A part cannot refer to a part which refers to it + + - If the "sub_part" is trackable, then the "part" must be trackable too! """ # If the sub_part is 'trackable' then the 'quantity' field must be an integer @@ -1730,6 +1802,13 @@ class BomItem(models.Model): raise ValidationError({ "quantity": _("Quantity must be integer value for trackable parts") }) + + # Force the upstream part to be trackable if the sub_part is trackable + if not self.part.trackable: + self.part.trackable = True + self.part.clean() + self.part.save() + except Part.DoesNotExist: pass @@ -1842,3 +1921,71 @@ class BomItem(models.Model): pmax = decimal2string(pmax) return "{pmin} to {pmax}".format(pmin=pmin, pmax=pmax) + + +class PartRelated(models.Model): + """ Store and handle related parts (eg. mating connector, crimps, etc.) """ + + part_1 = models.ForeignKey(Part, related_name='related_parts_1', + on_delete=models.DO_NOTHING) + + part_2 = models.ForeignKey(Part, related_name='related_parts_2', + on_delete=models.DO_NOTHING, + help_text=_('Select Related Part')) + + def __str__(self): + return f'{self.part_1} <--> {self.part_2}' + + def validate(self, part_1, part_2): + ''' Validate that the two parts relationship is unique ''' + + validate = True + + parts = Part.objects.all() + related_parts = PartRelated.objects.all() + + # Check if part exist and there are not the same part + if (part_1 in parts and part_2 in parts) and (part_1.pk != part_2.pk): + # Check if relation exists already + for relation in related_parts: + if (part_1 == relation.part_1 and part_2 == relation.part_2) \ + or (part_1 == relation.part_2 and part_2 == relation.part_1): + validate = False + break + else: + validate = False + + return validate + + def clean(self): + ''' Overwrite clean method to check that relation is unique ''' + + validate = self.validate(self.part_1, self.part_2) + + if not validate: + error_message = _('Error creating relationship: check that ' + 'the part is not related to itself ' + 'and that the relationship is unique') + + raise ValidationError(error_message) + + def create_relationship(self, part_1, part_2): + ''' Create relationship between two parts ''' + + validate = self.validate(part_1, part_2) + + if validate: + # Add relationship + self.part_1 = part_1 + self.part_2 = part_2 + self.save() + + return validate + + @classmethod + def create(cls, part_1, part_2): + ''' Create PartRelated object and relationship between two parts ''' + + related_part = cls() + related_part.create_relationship(part_1, part_2) + return related_part diff --git a/InvenTree/part/templates/part/bom.html b/InvenTree/part/templates/part/bom.html index a79e8b4dc2..38d07cb00e 100644 --- a/InvenTree/part/templates/part/bom.html +++ b/InvenTree/part/templates/part/bom.html @@ -10,13 +10,9 @@ {% include 'part/tabs.html' with tab='bom' %} -

    {% trans "Bill of Materials" %}

    +

    {% trans "Bill of Materials" %}

    +
    -{% if part.has_complete_bom_pricing == False %} -
    - The BOM for {{ part.full_name }} does not have complete pricing information -
    -{% endif %} {% if part.bom_checked_date %} {% if part.is_bom_valid %}
    diff --git a/InvenTree/part/templates/part/build.html b/InvenTree/part/templates/part/build.html index bfd72a2f70..9641181af8 100644 --- a/InvenTree/part/templates/part/build.html +++ b/InvenTree/part/templates/part/build.html @@ -5,13 +5,14 @@ {% include 'part/tabs.html' with tab='build' %} -

    {% trans "Part Builds" %}

    +

    {% trans "Part Builds" %}

    +
    {% if part.active %} {% if roles.build.add %} - + {% endif %} {% endif %}
    @@ -29,14 +30,11 @@ {% block js_ready %} {{ block.super }} $("#start-build").click(function() { - launchModalForm( - "{% url 'build-create' %}", - { - follow: true, - data: { - part: {{ part.id }} - } - }); + newBuildOrder({ + data: { + part: {{ part.id }}, + } + }); }); loadBuildTable($("#build-table"), { diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index 7e14184b30..a3c11e5669 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -41,7 +41,7 @@ {% if part.getLatestSerialNumber %} {{ part.getLatestSerialNumber }} {% else %} - {% trans "No serial numbers recorded" %} + {% trans "No serial numbers recorded" %} {% endif %} diff --git a/InvenTree/part/templates/part/related.html b/InvenTree/part/templates/part/related.html new file mode 100644 index 0000000000..8c5cc074c8 --- /dev/null +++ b/InvenTree/part/templates/part/related.html @@ -0,0 +1,77 @@ +{% extends "part/part_base.html" %} +{% load static %} +{% load i18n %} + +{% block details %} + +{% include 'part/tabs.html' with tab='related-parts' %} + +

    {% trans "Related Parts" %}

    +
    + +
    +
    + {% if roles.part.change %} + + + {% endif %} +
    +
    + + + + + + + + + {% for item in part.get_related_parts %} + {% with part_related=item.0 part=item.1 %} + + + + {% endwith %} + {% endfor %} + + + + +{% endblock %} + +{% block js_ready %} +{{ block.super }} + + $('#table-related-part').inventreeTable({ + }); + + $("#add-related-part").click(function() { + launchModalForm("{% url 'part-related-create' %}", { + data: { + part: {{ part.id }}, + }, + reload: true, + }); + }); + + $('.delete-related-part').click(function() { + var button = $(this); + + launchModalForm(button.attr('url'), { + reload: true, + }); + }); + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/tabs.html b/InvenTree/part/templates/part/tabs.html index 8322a225bc..8bfaba4d89 100644 --- a/InvenTree/part/templates/part/tabs.html +++ b/InvenTree/part/templates/part/tabs.html @@ -63,6 +63,9 @@ {% endif %} + + {% trans "Related" %} {% if part.related_count > 0 %}{{ part.related_count }}{% endif %} + {% trans "Attachments" %} {% if part.attachment_count > 0 %}{{ part.attachment_count }}{% endif %} diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 5fbdda1d0e..9692e208e2 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -13,6 +13,19 @@ from common.models import InvenTreeSetting, ColorTheme register = template.Library() +@register.simple_tag() +def define(value, *args, **kwargs): + """ + Shortcut function to overcome the shortcomings of the django templating language + + Use as follows: {% define "hello_world" as hello %} + + Ref: https://stackoverflow.com/questions/1070398/how-to-set-a-value-of-a-variable-inside-a-template-code + """ + + return value + + @register.simple_tag() def decimal(x, *args, **kwargs): """ Simplified rendering of a decimal number """ diff --git a/InvenTree/part/test_bom_item.py b/InvenTree/part/test_bom_item.py index 91aa95c17c..a518ca1ddc 100644 --- a/InvenTree/part/test_bom_item.py +++ b/InvenTree/part/test_bom_item.py @@ -28,10 +28,12 @@ class BomItemTest(TestCase): self.assertEqual(self.bob.bom_count, 4) def test_in_bom(self): - parts = self.bob.required_parts() + parts = self.bob.getRequiredParts() self.assertIn(self.orphan, parts) + # TODO: Tests for multi-level BOMs + def test_used_in(self): self.assertEqual(self.bob.used_in_count, 0) self.assertEqual(self.orphan.used_in_count, 1) diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index 97330f1ced..1301df3c91 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -99,7 +99,7 @@ class PartTest(TestCase): self.assertIn(self.R1.name, barcode) def test_copy(self): - self.R2.deepCopy(self.R1, image=True, bom=True) + self.R2.deep_copy(self.R1, image=True, bom=True) def test_match_names(self): diff --git a/InvenTree/part/test_views.py b/InvenTree/part/test_views.py index d8c345d243..1ad5c33e45 100644 --- a/InvenTree/part/test_views.py +++ b/InvenTree/part/test_views.py @@ -201,6 +201,29 @@ class PartTests(PartViewTestCase): self.assertEqual(response.status_code, 200) +class PartRelatedTests(PartViewTestCase): + + def test_valid_create(self): + """ test creation of an attachment for a valid part """ + + response = self.client.get(reverse('part-related-create'), {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + # TODO - Create a new attachment using this view + + def test_invalid_create(self): + """ test creation of an attachment for an invalid part """ + + # TODO + pass + + def test_edit(self): + """ test editing an attachment """ + + # TODO + pass + + class PartAttachmentTests(PartViewTestCase): def test_valid_create(self): diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index 488f4fe12e..4373de2385 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -12,6 +12,11 @@ from django.conf.urls import url, include from . import views +part_related_urls = [ + url(r'^new/?', views.PartRelatedCreate.as_view(), name='part-related-create'), + url(r'^(?P\d+)/delete/?', views.PartRelatedDelete.as_view(), name='part-related-delete'), +] + part_attachment_urls = [ url(r'^new/?', views.PartAttachmentCreate.as_view(), name='part-attachment-create'), url(r'^(?P\d+)/edit/?', views.PartAttachmentEdit.as_view(), name='part-attachment-edit'), @@ -61,6 +66,7 @@ part_detail_urls = [ url(r'^sale-prices/', views.PartDetail.as_view(template_name='part/sale_prices.html'), name='part-sale-prices'), url(r'^tests/', views.PartDetail.as_view(template_name='part/part_tests.html'), name='part-test-templates'), url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'), + url(r'^related-parts/?', views.PartDetail.as_view(template_name='part/related.html'), name='part-related'), url(r'^attachments/?', views.PartDetail.as_view(template_name='part/attachments.html'), name='part-attachments'), url(r'^notes/?', views.PartNotes.as_view(), name='part-notes'), @@ -121,6 +127,9 @@ part_urls = [ # Part category url(r'^category/(?P\d+)/', include(part_category_urls)), + # Part related + url(r'^related-parts/', include(part_related_urls)), + # Part attachments url(r'^attachment/', include(part_attachment_urls)), diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 2450777cd9..099dd3c515 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -22,7 +22,7 @@ import os from rapidfuzz import fuzz from decimal import Decimal, InvalidOperation -from .models import PartCategory, Part, PartAttachment +from .models import PartCategory, Part, PartAttachment, PartRelated from .models import PartParameterTemplate, PartParameter from .models import PartCategoryParameterTemplate from .models import BomItem @@ -72,6 +72,85 @@ class PartIndex(InvenTreeRoleMixin, ListView): return context +class PartRelatedCreate(AjaxCreateView): + """ View for creating a new PartRelated object + + - The view only makes sense if a Part object is passed to it + """ + model = PartRelated + form_class = part_forms.CreatePartRelatedForm + ajax_form_title = _("Add Related Part") + ajax_template_name = "modal_form.html" + role_required = 'part.change' + + def get_initial(self): + """ Set parent part as part_1 field """ + + initials = {} + + part_id = self.request.GET.get('part', None) + + if part_id: + try: + initials['part_1'] = Part.objects.get(pk=part_id) + except (Part.DoesNotExist, ValueError): + pass + + return initials + + def get_form(self): + """ Create a form to upload a new PartRelated + + - Hide the 'part_1' field (parent part) + - Display parts which are not yet related + """ + + form = super(AjaxCreateView, self).get_form() + + form.fields['part_1'].widget = HiddenInput() + + try: + # Get parent part + parent_part = self.get_initial()['part_1'] + # Get existing related parts + related_parts = [related_part[1].pk for related_part in parent_part.get_related_parts()] + + # Build updated choice list excluding + # - parts already related to parent part + # - the parent part itself + updated_choices = [] + for choice in form.fields["part_2"].choices: + if (choice[0] not in related_parts) and (choice[0] != parent_part.pk): + updated_choices.append(choice) + + # Update choices for related part + form.fields['part_2'].choices = updated_choices + except KeyError: + pass + + return form + + def post_save(self): + """ Save PartRelated model (POST method does not) """ + + form = self.get_form() + + if form.is_valid(): + part_1 = form.cleaned_data['part_1'] + part_2 = form.cleaned_data['part_2'] + + PartRelated.create(part_1, part_2) + + +class PartRelatedDelete(AjaxDeleteView): + """ View for deleting a PartRelated object """ + + model = PartRelated + ajax_form_title = _("Delete Related Part") + context_object_name = "related" + role_required = 'part.change' + + class PartAttachmentCreate(AjaxCreateView): """ View for creating a new PartAttachment object @@ -84,10 +163,14 @@ class PartAttachmentCreate(AjaxCreateView): role_required = 'part.add' - def post_save(self): - """ Record the user that uploaded the attachment """ - self.object.user = self.request.user - self.object.save() + def save(self, form, **kwargs): + """ + Record the user that uploaded this attachment + """ + + attachment = form.save(commit=False) + attachment.user = self.request.user + attachment.save() def get_data(self): return { @@ -362,7 +445,7 @@ class MakePartVariant(AjaxCreateView): parameters_copy = str2bool(request.POST.get('parameters_copy', False)) # Copy relevent information from the template part - part.deepCopy(part_template, bom=bom_copy, parameters=parameters_copy) + part.deep_copy(part_template, bom=bom_copy, parameters=parameters_copy) return self.renderJsonResponse(request, form, data, context=context) @@ -475,7 +558,7 @@ class PartDuplicate(AjaxCreateView): original = self.get_part_to_copy() if original: - part.deepCopy(original, bom=bom_copy, parameters=parameters_copy) + part.deep_copy(original, bom=bom_copy, parameters=parameters_copy) try: data['url'] = part.get_absolute_url() @@ -894,7 +977,10 @@ class BomDuplicate(AjaxUpdateView): if not confirm: form.add_error('confirm', _('Confirm duplication of BOM from parent')) - def post_save(self, part, form): + def save(self, part, form): + """ + Duplicate BOM from the specified parent + """ parent = form.cleaned_data.get('parent', None) @@ -935,7 +1021,10 @@ class BomValidate(AjaxUpdateView): if not confirm: form.add_error('validate', _('Confirm that the BOM is valid')) - def post_save(self, part, form, **kwargs): + def save(self, part, form, **kwargs): + """ + Mark the BOM as validated + """ part.validate_bom(self.request.user) @@ -2371,7 +2460,7 @@ class BomItemCreate(AjaxCreateView): query = query.filter(active=True) # Eliminate any options that are already in the BOM! - query = query.exclude(id__in=[item.id for item in part.required_parts()]) + query = query.exclude(id__in=[item.id for item in part.getRequiredParts()]) form.fields['sub_part'].queryset = query @@ -2439,7 +2528,7 @@ class BomItemEdit(AjaxUpdateView): except ValueError: sub_part_id = -1 - existing = [item.pk for item in part.required_parts()] + existing = [item.pk for item in part.getRequiredParts()] if sub_part_id in existing: existing.remove(sub_part_id) diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index dabeaa20ea..5d71d00a0c 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -493,6 +493,12 @@ class StockList(generics.ListCreateAPIView): if build_order: queryset = queryset.filter(build_order=build_order) + is_building = params.get('is_building', None) + + if is_building: + is_building = str2bool(is_building) + queryset = queryset.filter(is_building=is_building) + sales_order = params.get('sales_order', None) if sales_order: diff --git a/InvenTree/stock/fixtures/stock.yaml b/InvenTree/stock/fixtures/stock.yaml index dd528775eb..719d8a34ce 100644 --- a/InvenTree/stock/fixtures/stock.yaml +++ b/InvenTree/stock/fixtures/stock.yaml @@ -41,6 +41,7 @@ - model: stock.stockitem pk: 100 fields: + batch: "B1234" part: 25 location: 7 quantity: 10 @@ -54,6 +55,7 @@ pk: 101 fields: part: 25 + batch: "B2345" location: 7 quantity: 5 level: 0 @@ -65,6 +67,7 @@ pk: 102 fields: part: 25 + batch: 'ABCDE' location: 7 quantity: 3 level: 0 @@ -91,6 +94,7 @@ part: 10001 location: 7 quantity: 5 + batch: "AAA" level: 0 tree_id: 0 lft: 0 @@ -101,6 +105,7 @@ fields: part: 10001 location: 7 + batch: "AAA" quantity: 1 serial: 1 level: 0 diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 548a03ae90..d9937f3106 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -108,7 +108,7 @@ class ConvertStockItemForm(HelperForm): class CreateStockItemForm(HelperForm): """ Form for creating a new StockItem """ - serial_numbers = forms.CharField(label='Serial numbers', required=False, help_text=_('Enter unique serial numbers (or leave blank)')) + serial_numbers = forms.CharField(label=_('Serial numbers'), required=False, help_text=_('Enter unique serial numbers (or leave blank)')) def __init__(self, *args, **kwargs): diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 11ec0bd087..2ec42dd2f3 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -130,7 +130,7 @@ class StockItem(MPTTModel): status: Status of this StockItem (ref: InvenTree.status_codes.StockStatus) notes: Extra notes field build: Link to a Build (if this stock item was created from a build) - is_building: Boolean field indicating if this stock item is currently being built + is_building: Boolean field indicating if this stock item is currently being built (or is "in production") 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) @@ -139,6 +139,7 @@ class StockItem(MPTTModel): # A Query filter which will be re-used in multiple places to determine if a StockItem is actually "in stock" IN_STOCK_FILTER = Q( + quantity__gt=0, sales_order=None, build_order=None, belongs_to=None, @@ -174,7 +175,7 @@ class StockItem(MPTTModel): if add_note: # This StockItem is being saved for the first time self.addTransactionNote( - 'Created stock item', + _('Created stock item'), user, notes="Created new stock item for part '{p}'".format(p=str(self.part)), system=True @@ -198,7 +199,8 @@ class StockItem(MPTTModel): """ super(StockItem, self).validate_unique(exclude) - + + # If the serial number is set, make sure it is not a duplicate if self.serial is not None: # Query to look for duplicate serial numbers parts = PartModels.Part.objects.filter(tree_id=self.part.tree_id) @@ -718,6 +720,15 @@ class StockItem(MPTTModel): @property def in_stock(self): + """ + Returns True if this item is in stock + + See also: IN_STOCK_FILTER + """ + + # Quantity must be above zero (unless infinite) + if self.quantity <= 0 and not self.infinite: + return False # Not 'in stock' if it has been installed inside another StockItem if self.belongs_to is not None: @@ -1049,7 +1060,7 @@ class StockItem(MPTTModel): if self.updateQuantity(count): - self.addTransactionNote('Stocktake - counted {n} items'.format(n=count), + self.addTransactionNote('Stocktake - counted {n} items'.format(n=helpers.normalize(count)), user, notes=notes, system=True) @@ -1078,7 +1089,7 @@ class StockItem(MPTTModel): if self.updateQuantity(self.quantity + quantity): - self.addTransactionNote('Added {n} items to stock'.format(n=quantity), + self.addTransactionNote('Added {n} items to stock'.format(n=helpers.normalize(quantity)), user, notes=notes, system=True) @@ -1104,7 +1115,7 @@ class StockItem(MPTTModel): if self.updateQuantity(self.quantity - quantity): - self.addTransactionNote('Removed {n} items from stock'.format(n=quantity), + self.addTransactionNote('Removed {n} items from stock'.format(n=helpers.normalize(quantity)), user, notes=notes, system=True) diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 4a9b5a886b..5bea1c4aae 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -154,9 +154,11 @@ class StockItemSerializer(InvenTreeModelSerializer): 'allocated', 'batch', 'belongs_to', - 'customer', + 'build', 'build_order', + 'customer', 'in_stock', + 'is_building', 'link', 'location', 'location_detail', diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index e98ff37d93..71bdc3592d 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -15,6 +15,20 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% block pre_content %} {% include 'stock/loc_link.html' with location=item.location %} +{% if item.is_building %} +
    + {% trans "This stock item is in production and cannot be edited." %}
    + {% trans "Edit the stock item from the build view." %}
    + + {% if item.build %} + + {{ item.build }} + + {% endif %} + +
    +{% endif %} + {% if item.hasRequiredTests and not item.passedAllRequiredTests %}
    {% trans "This stock item has not passed all required tests" %} @@ -23,13 +37,13 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% for allocation in item.sales_order_allocations.all %}
    - {% trans "This stock item is allocated to Sales Order" %} #{{ allocation.line.order.reference }} ({% trans "Quantity" %}: {% decimal allocation.quantity %}) + {% trans "This stock item is allocated to Sales Order" %} #{{ allocation.line.order }} ({% trans "Quantity" %}: {% decimal allocation.quantity %})
    {% endfor %} {% for allocation in item.allocations.all %}
    - {% trans "This stock item is allocated to Build" %} #{{ allocation.build.id }} ({% trans "Quantity" %}: {% decimal allocation.quantity %}) + {% trans "This stock item is allocated to Build" %} #{{ allocation.build }} ({% trans "Quantity" %}: {% decimal allocation.quantity %})
    {% endfor %} @@ -79,7 +93,6 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
    -
    @@ -99,7 +112,7 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
    - {% if roles.stock.change %} + {% if roles.stock.change and not item.is_building %}
    {% endif %} - {% if roles.stock.change %} + {% if roles.stock.change and not item.is_building %}
    +
    diff --git a/InvenTree/templates/stock_table.html b/InvenTree/templates/stock_table.html index 9308f1912c..51f7c277db 100644 --- a/InvenTree/templates/stock_table.html +++ b/InvenTree/templates/stock_table.html @@ -18,14 +18,14 @@
    @@ -39,4 +39,4 @@
    -
    \ No newline at end of file + diff --git a/InvenTree/users/admin.py b/InvenTree/users/admin.py index e4974a3f7a..4614e5bc4c 100644 --- a/InvenTree/users/admin.py +++ b/InvenTree/users/admin.py @@ -3,12 +3,13 @@ from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ -from django.contrib import admin +from django.contrib import admin, messages from django import forms from django.contrib.auth import get_user_model from django.contrib.admin.widgets import FilteredSelectMultiple from django.contrib.auth.models import Group from django.contrib.auth.admin import UserAdmin +from django.utils.safestring import mark_safe from users.models import RuleSet @@ -94,15 +95,37 @@ class RoleGroupAdmin(admin.ModelAdmin): filter_horizontal = ['permissions'] - # Save inlines before model - # https://stackoverflow.com/a/14860703/12794913 def save_model(self, request, obj, form, change): - pass # don't actually save the parent instance + """ + This method serves two purposes: + - show warning message whenever the group users belong to multiple groups + - skip saving of the group instance model as inlines needs to be saved before. + """ + + # Get form cleaned data + users = form.cleaned_data['users'] + + # Check for users who are members of multiple groups + warning_message = '' + for user in users: + if user.groups.all().count() > 1: + warning_message += f'
    - {user.username} is member of: ' + for idx, group in enumerate(user.groups.all()): + warning_message += f'{group.name}' + if idx < len(user.groups.all()) - 1: + warning_message += ', ' + + # If any, display warning message when group is saved + if warning_message: + warning_message = mark_safe(_(f'The following users are members of multiple groups:' + f'{warning_message}')) + messages.add_message(request, messages.WARNING, warning_message) def save_formset(self, request, form, formset, change): - formset.save() # this will save the children - # update_fields is required to trigger permissions update - form.instance.save(update_fields=['name']) # form.instance is the parent + # Save inline Rulesets + formset.save() + # Save Group instance and update permissions + form.instance.save(update_fields=['name']) class InvenTreeUserAdmin(UserAdmin): diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index 7e65ba248e..b07ae16c31 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_partrelated', 'part_partcategoryparametertemplate', ], 'stock': [ @@ -72,6 +73,7 @@ class RuleSet(models.Model): 'part_bomitem', 'build_build', 'build_builditem', + 'build_buildorderattachment', 'stock_stockitem', 'stock_stocklocation', ], diff --git a/tasks.py b/tasks.py index 1f7f8cd795..49f3f9445b 100644 --- a/tasks.py +++ b/tasks.py @@ -171,7 +171,8 @@ def translate(c): or after adding translations for existing strings. """ - manage(c, "makemessages -e html -e py -e js") + # Translate applicable .py / .html / .js files + manage(c, "makemessages -e py -e html -e js") manage(c, "compilemessages") @task