mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
75f8d8b391
31
.github/workflows/release.yml
vendored
Normal file
31
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
# Runs on releases
|
||||
|
||||
name: Publish release notes
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
tweet:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: ethomson/send-tweet-action@v1
|
||||
with:
|
||||
status: "InvenTree release ${{ github.event.release.tag_name }} is out now! Release notes: ${{ github.event.release.html_url }} #opensource #inventree"
|
||||
consumer-key: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
|
||||
consumer-secret: ${{ secrets.TWITTER_CONSUMER_API_SECRET }}
|
||||
access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }}
|
||||
access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
|
||||
|
||||
reddit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: bluwy/release-for-reddit-action@v1
|
||||
with:
|
||||
username: ${{ secrets.REDDIT_USERNAME }}
|
||||
password: ${{ secrets.REDDIT_PASSWORD }}
|
||||
app-id: ${{ secrets.REDDIT_APP_ID }}
|
||||
app-secret: ${{ secrets.REDDIT_APP_SECRET }}
|
||||
subreddit: InvenTree
|
||||
title: "InvenTree version ${{ github.event.release.tag_name }} released"
|
||||
comment: "${{ github.event.release.body }}"
|
@ -133,8 +133,12 @@ class InvenTreeAPITestCase(UserMixin, APITestCase):
|
||||
if expected_code is not None:
|
||||
|
||||
if response.status_code != expected_code:
|
||||
print(f"Unexpected response at '{url}':")
|
||||
print(response.data)
|
||||
print(f"Unexpected response at '{url}': status code = {response.status_code}")
|
||||
|
||||
if hasattr(response, 'data'):
|
||||
print(response.data)
|
||||
else:
|
||||
print(f"(response object {type(response)} has no 'data' attribute")
|
||||
|
||||
self.assertEqual(response.status_code, expected_code)
|
||||
|
||||
|
@ -2,11 +2,15 @@
|
||||
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 61
|
||||
INVENTREE_API_VERSION = 64
|
||||
|
||||
"""
|
||||
Increment this API version number whenever there is a significant change to the API that any clients need to know about
|
||||
|
||||
v64 -> 2022-07-08 : https://github.com/inventree/InvenTree/pull/3310
|
||||
- Annotate 'on_order' quantity to BOM list API
|
||||
- Allow BOM List API endpoint to be filtered by "on_order" parameter
|
||||
|
||||
v63 -> 2022-07-06 : https://github.com/inventree/InvenTree/pull/3301
|
||||
- Allow BOM List API endpoint to be filtered by "available_stock" paramater
|
||||
|
||||
|
@ -63,10 +63,15 @@ class InvenTreeModelMoneyField(ModelMoneyField):
|
||||
# Set a minimum value validator
|
||||
validators = kwargs.get('validators', [])
|
||||
|
||||
allow_negative = kwargs.pop('allow_negative', False)
|
||||
|
||||
# If no validators are provided, add some "standard" ones
|
||||
if len(validators) == 0:
|
||||
validators.append(
|
||||
MinMoneyValidator(0),
|
||||
)
|
||||
|
||||
if not allow_negative:
|
||||
validators.append(
|
||||
MinMoneyValidator(0),
|
||||
)
|
||||
|
||||
kwargs['validators'] = validators
|
||||
|
||||
|
156
InvenTree/InvenTree/format.py
Normal file
156
InvenTree/InvenTree/format.py
Normal file
@ -0,0 +1,156 @@
|
||||
"""Custom string formatting functions and helpers"""
|
||||
|
||||
import re
|
||||
import string
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
def parse_format_string(fmt_string: str) -> dict:
|
||||
"""Extract formatting information from the provided format string.
|
||||
|
||||
Returns a dict object which contains structured information about the format groups
|
||||
"""
|
||||
|
||||
groups = string.Formatter().parse(fmt_string)
|
||||
|
||||
info = {}
|
||||
|
||||
for group in groups:
|
||||
# Skip any group which does not have a named value
|
||||
if not group[1]:
|
||||
continue
|
||||
|
||||
info[group[1]] = {
|
||||
'format': group[1],
|
||||
'prefix': group[0],
|
||||
}
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def construct_format_regex(fmt_string: str) -> str:
|
||||
r"""Construct a regular expression based on a provided format string
|
||||
|
||||
This function turns a python format string into a regular expression,
|
||||
which can be used for two purposes:
|
||||
|
||||
- Ensure that a particular string matches the specified format
|
||||
- Extract named variables from a matching string
|
||||
|
||||
This function also provides support for wildcard characters:
|
||||
|
||||
- '?' provides single character matching; is converted to a '.' (period) for regex
|
||||
- '#' provides single digit matching; is converted to '\d'
|
||||
|
||||
Args:
|
||||
fmt_string: A typical format string e.g. "PO-???-{ref:04d}"
|
||||
|
||||
Returns:
|
||||
str: A regular expression pattern e.g. ^PO\-...\-(?P<ref>.*)$
|
||||
|
||||
Raises:
|
||||
ValueError: Format string is invalid
|
||||
"""
|
||||
|
||||
pattern = "^"
|
||||
|
||||
for group in string.Formatter().parse(fmt_string):
|
||||
prefix = group[0] # Prefix (literal text appearing before this group)
|
||||
name = group[1] # Name of this format variable
|
||||
format = group[2] # Format specifier e.g :04d
|
||||
|
||||
rep = [
|
||||
'+', '-', '.',
|
||||
'{', '}', '(', ')',
|
||||
'^', '$', '~', '!', '@', ':', ';', '|', '\'', '"',
|
||||
]
|
||||
|
||||
# Escape any special regex characters
|
||||
for ch in rep:
|
||||
prefix = prefix.replace(ch, '\\' + ch)
|
||||
|
||||
# Replace ? with single-character match
|
||||
prefix = prefix.replace('?', '.')
|
||||
|
||||
# Replace # with single-digit match
|
||||
prefix = prefix.replace('#', r'\d')
|
||||
|
||||
pattern += prefix
|
||||
|
||||
# Add a named capture group for the format entry
|
||||
if name:
|
||||
|
||||
# Check if integer values are requried
|
||||
if format.endswith('d'):
|
||||
chr = '\d'
|
||||
else:
|
||||
chr = '.'
|
||||
|
||||
# Specify width
|
||||
# TODO: Introspect required width
|
||||
w = '+'
|
||||
|
||||
pattern += f"(?P<{name}>{chr}{w})"
|
||||
|
||||
pattern += "$"
|
||||
|
||||
return pattern
|
||||
|
||||
|
||||
def validate_string(value: str, fmt_string: str) -> str:
|
||||
"""Validate that the provided string matches the specified format.
|
||||
|
||||
Args:
|
||||
value: The string to be tested e.g. 'SO-1234-ABC',
|
||||
fmt_string: The required format e.g. 'SO-{ref}-???',
|
||||
|
||||
Returns:
|
||||
bool: True if the value matches the required format, else False
|
||||
|
||||
Raises:
|
||||
ValueError: The provided format string is invalid
|
||||
"""
|
||||
|
||||
pattern = construct_format_regex(fmt_string)
|
||||
|
||||
result = re.match(pattern, value)
|
||||
|
||||
return result is not None
|
||||
|
||||
|
||||
def extract_named_group(name: str, value: str, fmt_string: str) -> str:
|
||||
"""Extract a named value from the provided string, given the provided format string
|
||||
|
||||
Args:
|
||||
name: Name of group to extract e.g. 'ref'
|
||||
value: Raw string e.g. 'PO-ABC-1234'
|
||||
fmt_string: Format pattern e.g. 'PO-???-{ref}
|
||||
|
||||
Returns:
|
||||
str: String value of the named group
|
||||
|
||||
Raises:
|
||||
ValueError: format string is incorrectly specified, or provided value does not match format string
|
||||
NameError: named value does not exist in the format string
|
||||
IndexError: named value could not be found in the provided entry
|
||||
"""
|
||||
|
||||
info = parse_format_string(fmt_string)
|
||||
|
||||
if name not in info.keys():
|
||||
raise NameError(_(f"Value '{name}' does not appear in pattern format"))
|
||||
|
||||
# Construct a regular expression for matching against the provided format string
|
||||
# Note: This will raise a ValueError if 'fmt_string' is incorrectly specified
|
||||
pattern = construct_format_regex(fmt_string)
|
||||
|
||||
# Run the regex matcher against the raw string
|
||||
result = re.match(pattern, value)
|
||||
|
||||
if not result:
|
||||
raise ValueError(_("Provided value does not match required pattern: ") + fmt_string)
|
||||
|
||||
# And return the value we are interested in
|
||||
# Note: This will raise an IndexError if the named group was not matched
|
||||
return result.group(name)
|
@ -15,7 +15,6 @@ from allauth.account.forms import SignupForm, set_form_field_order
|
||||
from allauth.exceptions import ImmediateHttpResponse
|
||||
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
||||
from allauth_2fa.adapter import OTPAdapter
|
||||
from allauth_2fa.forms import TOTPDeviceRemoveForm
|
||||
from allauth_2fa.utils import user_has_valid_totp_device
|
||||
from crispy_forms.bootstrap import (AppendedText, PrependedAppendedText,
|
||||
PrependedText)
|
||||
@ -270,36 +269,3 @@ class CustomSocialAccountAdapter(RegistratonMixin, DefaultSocialAccountAdapter):
|
||||
|
||||
# Otherwise defer to the original allauth adapter.
|
||||
return super().login(request, user)
|
||||
|
||||
|
||||
# Temporary fix for django-allauth-2fa # TODO remove
|
||||
# See https://github.com/inventree/InvenTree/security/advisories/GHSA-8j76-mm54-52xq
|
||||
|
||||
class CustomTOTPDeviceRemoveForm(TOTPDeviceRemoveForm):
|
||||
"""Custom Form to ensure a token is provided before removing MFA"""
|
||||
# User must input a valid token so 2FA can be removed
|
||||
token = forms.CharField(
|
||||
label=_('Token'),
|
||||
)
|
||||
|
||||
def __init__(self, user, **kwargs):
|
||||
"""Add token field."""
|
||||
super().__init__(user, **kwargs)
|
||||
self.fields['token'].widget.attrs.update(
|
||||
{
|
||||
'autofocus': 'autofocus',
|
||||
'autocomplete': 'off',
|
||||
}
|
||||
)
|
||||
|
||||
def clean_token(self):
|
||||
"""Ensure at least one valid token is provided."""
|
||||
# Ensure that the user has provided a valid token
|
||||
token = self.cleaned_data.get('token')
|
||||
|
||||
# Verify that the user has provided a valid token
|
||||
for device in self.user.totpdevice_set.filter(confirmed=True):
|
||||
if device.verify_token(token):
|
||||
return token
|
||||
|
||||
raise forms.ValidationError(_("The entered token is not valid"))
|
||||
|
@ -3,6 +3,7 @@
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
@ -19,7 +20,9 @@ from error_report.models import Error
|
||||
from mptt.exceptions import InvalidMove
|
||||
from mptt.models import MPTTModel, TreeForeignKey
|
||||
|
||||
import InvenTree.format
|
||||
import InvenTree.helpers
|
||||
from common.models import InvenTreeSetting
|
||||
from InvenTree.fields import InvenTreeURLField
|
||||
from InvenTree.validators import validate_tree_name
|
||||
|
||||
@ -96,9 +99,6 @@ class DataImportMixin(object):
|
||||
class ReferenceIndexingMixin(models.Model):
|
||||
"""A mixin for keeping track of numerical copies of the "reference" field.
|
||||
|
||||
!!DANGER!! always add `ReferenceIndexingSerializerMixin`to all your models serializers to
|
||||
ensure the reference field is not too big
|
||||
|
||||
Here, we attempt to convert a "reference" field value (char) to an integer,
|
||||
for performing fast natural sorting.
|
||||
|
||||
@ -112,24 +112,216 @@ class ReferenceIndexingMixin(models.Model):
|
||||
- Otherwise, we store zero
|
||||
"""
|
||||
|
||||
# Name of the global setting which defines the required reference pattern for this model
|
||||
REFERENCE_PATTERN_SETTING = None
|
||||
|
||||
@classmethod
|
||||
def get_reference_pattern(cls):
|
||||
"""Returns the reference pattern associated with this model.
|
||||
|
||||
This is defined by a global setting object, specified by the REFERENCE_PATTERN_SETTING attribute
|
||||
"""
|
||||
|
||||
# By default, we return an empty string
|
||||
if cls.REFERENCE_PATTERN_SETTING is None:
|
||||
return ''
|
||||
|
||||
return InvenTreeSetting.get_setting(cls.REFERENCE_PATTERN_SETTING, create=False).strip()
|
||||
|
||||
@classmethod
|
||||
def get_reference_context(cls):
|
||||
"""Generate context data for generating the 'reference' field for this class.
|
||||
|
||||
- Returns a python dict object which contains the context data for formatting the reference string.
|
||||
- The default implementation provides some default context information
|
||||
"""
|
||||
|
||||
return {
|
||||
'ref': cls.get_next_reference(),
|
||||
'date': datetime.now(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_most_recent_item(cls):
|
||||
"""Return the item which is 'most recent'
|
||||
|
||||
In practice, this means the item with the highest reference value
|
||||
"""
|
||||
|
||||
query = cls.objects.all().order_by('-reference_int', '-pk')
|
||||
|
||||
if query.exists():
|
||||
return query.first()
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_next_reference(cls):
|
||||
"""Return the next available reference value for this particular class."""
|
||||
|
||||
# Find the "most recent" item
|
||||
latest = cls.get_most_recent_item()
|
||||
|
||||
if not latest:
|
||||
# No existing items
|
||||
return 1
|
||||
|
||||
reference = latest.reference.strip
|
||||
|
||||
try:
|
||||
reference = InvenTree.format.extract_named_group('ref', reference, cls.get_reference_pattern())
|
||||
except Exception:
|
||||
# If reference cannot be extracted using the pattern, try just the integer value
|
||||
reference = str(latest.reference_int)
|
||||
|
||||
# Attempt to perform 'intelligent' incrementing of the reference field
|
||||
incremented = InvenTree.helpers.increment(reference)
|
||||
|
||||
try:
|
||||
incremented = int(incremented)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return incremented
|
||||
|
||||
@classmethod
|
||||
def generate_reference(cls):
|
||||
"""Generate the next 'reference' field based on specified pattern"""
|
||||
|
||||
fmt = cls.get_reference_pattern()
|
||||
ctx = cls.get_reference_context()
|
||||
|
||||
reference = None
|
||||
|
||||
attempts = set()
|
||||
|
||||
while reference is None:
|
||||
try:
|
||||
ref = fmt.format(**ctx)
|
||||
|
||||
if ref in attempts:
|
||||
# We are stuck in a loop!
|
||||
reference = ref
|
||||
break
|
||||
else:
|
||||
attempts.add(ref)
|
||||
|
||||
if cls.objects.filter(reference=ref).exists():
|
||||
# Handle case where we have duplicated an existing reference
|
||||
ctx['ref'] = InvenTree.helpers.increment(ctx['ref'])
|
||||
else:
|
||||
# We have found an 'unused' reference
|
||||
reference = ref
|
||||
break
|
||||
|
||||
except Exception:
|
||||
# If anything goes wrong, return the most recent reference
|
||||
recent = cls.get_most_recent_item()
|
||||
if recent:
|
||||
reference = recent.reference
|
||||
else:
|
||||
reference = ""
|
||||
|
||||
return reference
|
||||
|
||||
@classmethod
|
||||
def validate_reference_pattern(cls, pattern):
|
||||
"""Ensure that the provided pattern is valid"""
|
||||
|
||||
ctx = cls.get_reference_context()
|
||||
|
||||
try:
|
||||
info = InvenTree.format.parse_format_string(pattern)
|
||||
except Exception:
|
||||
raise ValidationError({
|
||||
"value": _("Improperly formatted pattern"),
|
||||
})
|
||||
|
||||
# Check that only 'allowed' keys are provided
|
||||
for key in info.keys():
|
||||
if key not in ctx.keys():
|
||||
raise ValidationError({
|
||||
"value": _("Unknown format key specified") + f": '{key}'"
|
||||
})
|
||||
|
||||
# Check that the 'ref' variable is specified
|
||||
if 'ref' not in info.keys():
|
||||
raise ValidationError({
|
||||
'value': _("Missing required format key") + ": 'ref'"
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def validate_reference_field(cls, value):
|
||||
"""Check that the provided 'reference' value matches the requisite pattern"""
|
||||
|
||||
pattern = cls.get_reference_pattern()
|
||||
|
||||
value = str(value).strip()
|
||||
|
||||
if len(value) == 0:
|
||||
raise ValidationError(_("Reference field cannot be empty"))
|
||||
|
||||
# An 'empty' pattern means no further validation is required
|
||||
if not pattern:
|
||||
return
|
||||
|
||||
if not InvenTree.format.validate_string(value, pattern):
|
||||
raise ValidationError(_("Reference must match required pattern") + ": " + pattern)
|
||||
|
||||
# Check that the reference field can be rebuild
|
||||
cls.rebuild_reference_field(value, validate=True)
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options. Abstract ensures no database table is created."""
|
||||
|
||||
abstract = True
|
||||
|
||||
def rebuild_reference_field(self):
|
||||
"""Extract integer out of reference for sorting."""
|
||||
reference = getattr(self, 'reference', '')
|
||||
self.reference_int = extract_int(reference)
|
||||
@classmethod
|
||||
def rebuild_reference_field(cls, reference, validate=False):
|
||||
"""Extract integer out of reference for sorting.
|
||||
|
||||
If the 'integer' portion is buried somewhere 'within' the reference,
|
||||
we can first try to extract it using the pattern.
|
||||
|
||||
Example:
|
||||
reference - BO-123-ABC
|
||||
pattern - BO-{ref}-???
|
||||
extracted - 123
|
||||
|
||||
If we cannot extract using the pattern for some reason, fallback to the entire reference
|
||||
"""
|
||||
|
||||
try:
|
||||
# Extract named group based on provided pattern
|
||||
reference = InvenTree.format.extract_named_group('ref', reference, cls.get_reference_pattern())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
reference_int = extract_int(reference)
|
||||
|
||||
if validate:
|
||||
if reference_int > models.BigIntegerField.MAX_BIGINT:
|
||||
raise ValidationError({
|
||||
"reference": _("Reference number is too large")
|
||||
})
|
||||
|
||||
return reference_int
|
||||
|
||||
reference_int = models.BigIntegerField(default=0)
|
||||
|
||||
|
||||
def extract_int(reference, clip=0x7fffffff):
|
||||
"""Extract integer out of reference."""
|
||||
def extract_int(reference, clip=0x7fffffff, allow_negative=False):
|
||||
"""Extract an integer out of reference."""
|
||||
|
||||
# Default value if we cannot convert to an integer
|
||||
ref_int = 0
|
||||
|
||||
reference = str(reference).strip()
|
||||
|
||||
# Ignore empty string
|
||||
if len(reference) == 0:
|
||||
return 0
|
||||
|
||||
# Look at the start of the string - can it be "integerized"?
|
||||
result = re.match(r"^(\d+)", reference)
|
||||
|
||||
@ -139,6 +331,16 @@ def extract_int(reference, clip=0x7fffffff):
|
||||
ref_int = int(ref)
|
||||
except Exception:
|
||||
ref_int = 0
|
||||
else:
|
||||
# Look at the "end" of the string
|
||||
result = re.search(r'(\d+)$', reference)
|
||||
|
||||
if result and len(result.groups()) == 1:
|
||||
ref = result.groups()[0]
|
||||
try:
|
||||
ref_int = int(ref)
|
||||
except Exception:
|
||||
ref_int = 0
|
||||
|
||||
# Ensure that the returned values are within the range that can be stored in an IntegerField
|
||||
# Note: This will result in large values being "clipped"
|
||||
@ -148,6 +350,9 @@ def extract_int(reference, clip=0x7fffffff):
|
||||
elif ref_int < -clip:
|
||||
ref_int = -clip
|
||||
|
||||
if not allow_negative and ref_int < 0:
|
||||
ref_int = abs(ref_int)
|
||||
|
||||
return ref_int
|
||||
|
||||
|
||||
|
@ -7,7 +7,6 @@ from decimal import Decimal
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import tablib
|
||||
@ -20,8 +19,6 @@ from rest_framework.fields import empty
|
||||
from rest_framework.serializers import DecimalField
|
||||
from rest_framework.utils import model_meta
|
||||
|
||||
from .models import extract_int
|
||||
|
||||
|
||||
class InvenTreeMoneySerializer(MoneyField):
|
||||
"""Custom serializer for 'MoneyField', which ensures that passed values are numerically valid.
|
||||
@ -211,16 +208,6 @@ class UserSerializer(InvenTreeModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class ReferenceIndexingSerializerMixin():
|
||||
"""This serializer mixin ensures the the reference is not to big / small for the BigIntegerField."""
|
||||
|
||||
def validate_reference(self, value):
|
||||
"""Ensures the reference is not to big / small for the BigIntegerField."""
|
||||
if extract_int(value) > models.BigIntegerField.MAX_BIGINT:
|
||||
raise serializers.ValidationError('reference is to to big')
|
||||
return value
|
||||
|
||||
|
||||
class InvenTreeAttachmentSerializerField(serializers.FileField):
|
||||
"""Override the DRF native FileField serializer, to remove the leading server path.
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -40,8 +40,7 @@ function attachClipboard(selector, containerselector, textElement) {
|
||||
}
|
||||
|
||||
// create Clipboard
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
var cis = new ClipboardJS(selector, {
|
||||
new ClipboardJS(selector, {
|
||||
text: text,
|
||||
container: containerselector
|
||||
});
|
||||
@ -110,6 +109,9 @@ function inventreeDocReady() {
|
||||
$('#launch-about').click(function() {
|
||||
launchModalForm(`/about/`, {
|
||||
no_post: true,
|
||||
after_render: function() {
|
||||
attachClipboard('.clip-btn', 'modal-form');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -17,6 +17,7 @@ from djmoney.contrib.exchange.exceptions import MissingRate
|
||||
from djmoney.contrib.exchange.models import Rate, convert_money
|
||||
from djmoney.money import Money
|
||||
|
||||
import InvenTree.format
|
||||
import InvenTree.tasks
|
||||
from common.models import InvenTreeSetting
|
||||
from common.settings import currency_codes
|
||||
@ -60,6 +61,137 @@ class ValidatorTest(TestCase):
|
||||
validate_overage("aaaa")
|
||||
|
||||
|
||||
class FormatTest(TestCase):
|
||||
"""Unit tests for custom string formatting functionality"""
|
||||
|
||||
def test_parse(self):
|
||||
"""Tests for the 'parse_format_string' function"""
|
||||
|
||||
# Extract data from a valid format string
|
||||
fmt = "PO-{abc:02f}-{ref:04d}-{date}-???"
|
||||
|
||||
info = InvenTree.format.parse_format_string(fmt)
|
||||
|
||||
self.assertIn('abc', info)
|
||||
self.assertIn('ref', info)
|
||||
self.assertIn('date', info)
|
||||
|
||||
# Try with invalid strings
|
||||
for fmt in [
|
||||
'PO-{{xyz}',
|
||||
'PO-{xyz}}',
|
||||
'PO-{xyz}-{',
|
||||
]:
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
InvenTree.format.parse_format_string(fmt)
|
||||
|
||||
def test_create_regex(self):
|
||||
"""Test function for creating a regex from a format string"""
|
||||
|
||||
tests = {
|
||||
"PO-123-{ref:04f}": r"^PO\-123\-(?P<ref>.+)$",
|
||||
"{PO}-???-{ref}-{date}-22": r"^(?P<PO>.+)\-...\-(?P<ref>.+)\-(?P<date>.+)\-22$",
|
||||
"ABC-123-###-{ref}": r"^ABC\-123\-\d\d\d\-(?P<ref>.+)$",
|
||||
"ABC-123": r"^ABC\-123$",
|
||||
}
|
||||
|
||||
for fmt, reg in tests.items():
|
||||
self.assertEqual(InvenTree.format.construct_format_regex(fmt), reg)
|
||||
|
||||
def test_validate_format(self):
|
||||
"""Test that string validation works as expected"""
|
||||
|
||||
# These tests should pass
|
||||
for value, pattern in {
|
||||
"ABC-hello-123": "???-{q}-###",
|
||||
"BO-1234": "BO-{ref}",
|
||||
"111.222.fred.china": "???.###.{name}.{place}",
|
||||
"PO-1234": "PO-{ref:04d}"
|
||||
}.items():
|
||||
self.assertTrue(InvenTree.format.validate_string(value, pattern))
|
||||
|
||||
# These tests should fail
|
||||
for value, pattern in {
|
||||
"ABC-hello-123": "###-{q}-???",
|
||||
"BO-1234": "BO.{ref}",
|
||||
"BO-####": "BO-{pattern}-{next}",
|
||||
"BO-123d": "BO-{ref:04d}"
|
||||
}.items():
|
||||
self.assertFalse(InvenTree.format.validate_string(value, pattern))
|
||||
|
||||
def test_extract_value(self):
|
||||
"""Test that we can extract named values based on a format string"""
|
||||
|
||||
# Simple tests based on a straight-forward format string
|
||||
fmt = "PO-###-{ref:04d}"
|
||||
|
||||
tests = {
|
||||
"123": "PO-123-123",
|
||||
"456": "PO-123-456",
|
||||
"789": "PO-123-789",
|
||||
}
|
||||
|
||||
for k, v in tests.items():
|
||||
self.assertEqual(InvenTree.format.extract_named_group('ref', v, fmt), k)
|
||||
|
||||
# However these ones should fail
|
||||
tests = {
|
||||
'abc': 'PO-123-abc',
|
||||
'xyz': 'PO-123-xyz',
|
||||
}
|
||||
|
||||
for v in tests.values():
|
||||
with self.assertRaises(ValueError):
|
||||
InvenTree.format.extract_named_group('ref', v, fmt)
|
||||
|
||||
# More complex tests
|
||||
fmt = "PO-{date}-{test}-???-{ref}-###"
|
||||
val = "PO-2022-02-01-hello-ABC-12345-222"
|
||||
|
||||
data = {
|
||||
'date': '2022-02-01',
|
||||
'test': 'hello',
|
||||
'ref': '12345',
|
||||
}
|
||||
|
||||
for k, v in data.items():
|
||||
self.assertEqual(InvenTree.format.extract_named_group(k, val, fmt), v)
|
||||
|
||||
# Test for error conditions
|
||||
|
||||
# Raises a ValueError as the format string is bad
|
||||
with self.assertRaises(ValueError):
|
||||
InvenTree.format.extract_named_group(
|
||||
"test",
|
||||
"PO-1234-5",
|
||||
"PO-{test}-{"
|
||||
)
|
||||
|
||||
# Raises a NameError as the named group does not exist in the format string
|
||||
with self.assertRaises(NameError):
|
||||
InvenTree.format.extract_named_group(
|
||||
"missing",
|
||||
"PO-12345",
|
||||
"PO-{test}",
|
||||
)
|
||||
|
||||
# Raises a ValueError as the value does not match the format string
|
||||
with self.assertRaises(ValueError):
|
||||
InvenTree.format.extract_named_group(
|
||||
"test",
|
||||
"PO-1234",
|
||||
"PO-{test}-1234",
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
InvenTree.format.extract_named_group(
|
||||
"test",
|
||||
"PO-ABC-xyz",
|
||||
"PO-###-{test}",
|
||||
)
|
||||
|
||||
|
||||
class TestHelpers(TestCase):
|
||||
"""Tests for InvenTree helper functions."""
|
||||
|
||||
|
@ -57,17 +57,6 @@ def validate_part_ipn(value):
|
||||
raise ValidationError(_('IPN must match regex pattern {pat}').format(pat=pattern))
|
||||
|
||||
|
||||
def validate_build_order_reference(value):
|
||||
"""Validate the 'reference' field of a BuildOrder."""
|
||||
pattern = common.models.InvenTreeSetting.get_setting('BUILDORDER_REFERENCE_REGEX')
|
||||
|
||||
if pattern:
|
||||
match = re.search(pattern, value)
|
||||
|
||||
if match is None:
|
||||
raise ValidationError(_('Reference must match pattern {pattern}').format(pattern=pattern))
|
||||
|
||||
|
||||
def validate_purchase_order_reference(value):
|
||||
"""Validate the 'reference' field of a PurchaseOrder."""
|
||||
pattern = common.models.InvenTreeSetting.get_setting('PURCHASEORDER_REFERENCE_REGEX')
|
||||
|
@ -37,7 +37,7 @@ from common.settings import currency_code_default, currency_codes
|
||||
from part.models import PartCategory
|
||||
from users.models import RuleSet, check_user_role
|
||||
|
||||
from .forms import CustomTOTPDeviceRemoveForm, EditUserForm, SetPasswordForm
|
||||
from .forms import EditUserForm, SetPasswordForm
|
||||
|
||||
|
||||
def auth_request(request):
|
||||
@ -764,10 +764,8 @@ class NotificationsView(TemplateView):
|
||||
template_name = "InvenTree/notifications/notifications.html"
|
||||
|
||||
|
||||
# Temporary fix for django-allauth-2fa # TODO remove
|
||||
# See https://github.com/inventree/InvenTree/security/advisories/GHSA-8j76-mm54-52xq
|
||||
# Custom 2FA removal form to allow custom redirect URL
|
||||
|
||||
class CustomTwoFactorRemove(TwoFactorRemove):
|
||||
"""Use custom form."""
|
||||
form_class = CustomTOTPDeviceRemoveForm
|
||||
"""Specify custom URL redirect."""
|
||||
success_url = reverse_lazy("settings")
|
||||
|
@ -5,7 +5,7 @@
|
||||
fields:
|
||||
part: 100 # Build against part 100 "Bob"
|
||||
batch: 'B1'
|
||||
reference: "0001"
|
||||
reference: "BO-0001"
|
||||
title: 'Building 7 parts'
|
||||
quantity: 7
|
||||
notes: 'Some simple notes'
|
||||
@ -21,7 +21,7 @@
|
||||
pk: 2
|
||||
fields:
|
||||
part: 50
|
||||
reference: "0002"
|
||||
reference: "BO-0002"
|
||||
title: 'Making things'
|
||||
batch: 'B2'
|
||||
status: 40 # COMPLETE
|
||||
@ -37,7 +37,7 @@
|
||||
pk: 3
|
||||
fields:
|
||||
part: 50
|
||||
reference: "0003"
|
||||
reference: "BO-003"
|
||||
title: 'Making things'
|
||||
batch: 'B2'
|
||||
status: 40 # COMPLETE
|
||||
@ -53,7 +53,7 @@
|
||||
pk: 4
|
||||
fields:
|
||||
part: 50
|
||||
reference: "0004"
|
||||
reference: "BO-4"
|
||||
title: 'Making things'
|
||||
batch: 'B4'
|
||||
status: 40 # COMPLETE
|
||||
@ -69,7 +69,7 @@
|
||||
pk: 5
|
||||
fields:
|
||||
part: 25
|
||||
reference: "0005"
|
||||
reference: "BO-0005"
|
||||
title: "Building some Widgets"
|
||||
batch: "B10"
|
||||
status: 40 # Complete
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Generated by Django 3.0.7 on 2020-10-19 13:02
|
||||
|
||||
import InvenTree.validators
|
||||
import build.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@ -18,6 +18,6 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='reference',
|
||||
field=models.CharField(help_text='Build Order Reference', max_length=64, unique=True, validators=[InvenTree.validators.validate_build_order_reference], verbose_name='Reference'),
|
||||
field=models.CharField(help_text='Build Order Reference', max_length=64, unique=True, validators=[build.validators.validate_build_order_reference], verbose_name='Reference'),
|
||||
),
|
||||
]
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Generated by Django 3.2.4 on 2021-07-08 14:14
|
||||
|
||||
import InvenTree.validators
|
||||
import build.validators
|
||||
import build.models
|
||||
from django.db import migrations, models
|
||||
|
||||
@ -15,6 +15,6 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='reference',
|
||||
field=models.CharField(default=build.models.get_next_build_number, help_text='Build Order Reference', max_length=64, unique=True, validators=[InvenTree.validators.validate_build_order_reference], verbose_name='Reference'),
|
||||
field=models.CharField(default=build.validators.generate_next_build_reference, help_text='Build Order Reference', max_length=64, unique=True, validators=[build.validators.validate_build_order_reference], verbose_name='Reference'),
|
||||
),
|
||||
]
|
||||
|
69
InvenTree/build/migrations/0036_auto_20220707_1101.py
Normal file
69
InvenTree/build/migrations/0036_auto_20220707_1101.py
Normal file
@ -0,0 +1,69 @@
|
||||
# Generated by Django 3.2.14 on 2022-07-07 11:01
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def update_build_reference(apps, schema_editor):
|
||||
"""Update the build order reference.
|
||||
|
||||
Ref: https://github.com/inventree/InvenTree/pull/3267
|
||||
|
||||
Performs the following steps:
|
||||
- Extract existing 'prefix' value
|
||||
- Generate a build order pattern based on the prefix value
|
||||
- Update any existing build order references with the specified prefix
|
||||
"""
|
||||
|
||||
InvenTreeSetting = apps.get_model('common', 'inventreesetting')
|
||||
|
||||
try:
|
||||
prefix = InvenTreeSetting.objects.get(key='BUILDORDER_REFERENCE_PREFIX').value
|
||||
except Exception:
|
||||
prefix = 'BO-'
|
||||
|
||||
# Construct a reference pattern
|
||||
pattern = prefix + '{ref:04d}'
|
||||
|
||||
# Create or update the BuildOrder.reference pattern
|
||||
try:
|
||||
setting = InvenTreeSetting.objects.get(key='BUILDORDER_REFERENCE_PATTERN')
|
||||
setting.value = pattern
|
||||
setting.save()
|
||||
except InvenTreeSetting.DoesNotExist:
|
||||
setting = InvenTreeSetting.objects.create(
|
||||
key='BUILDORDER_REFERENCE_PATTERN',
|
||||
value=pattern,
|
||||
)
|
||||
|
||||
# Update any existing build order references with the prefix
|
||||
Build = apps.get_model('build', 'build')
|
||||
|
||||
n = 0
|
||||
|
||||
for build in Build.objects.all():
|
||||
if not build.reference.startswith(prefix):
|
||||
build.reference = prefix + build.reference
|
||||
build.save()
|
||||
n += 1
|
||||
|
||||
if n > 0:
|
||||
print(f"Updated reference field for {n} BuildOrder objects")
|
||||
|
||||
|
||||
def nupdate_build_reference(apps, schema_editor):
|
||||
"""Reverse migration code. Does nothing."""
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0035_alter_build_notes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
update_build_reference,
|
||||
reverse_code=nupdate_build_reference,
|
||||
)
|
||||
]
|
@ -22,12 +22,14 @@ from mptt.exceptions import InvalidMove
|
||||
from rest_framework import serializers
|
||||
|
||||
from InvenTree.status_codes import BuildStatus, StockStatus, StockHistoryCode
|
||||
from InvenTree.helpers import increment, getSetting, normalize, MakeBarcode, notify_responsible
|
||||
from InvenTree.helpers import increment, normalize, MakeBarcode, notify_responsible
|
||||
from InvenTree.models import InvenTreeAttachment, ReferenceIndexingMixin
|
||||
from InvenTree.validators import validate_build_order_reference
|
||||
|
||||
from build.validators import generate_next_build_reference, validate_build_order_reference
|
||||
|
||||
import InvenTree.fields
|
||||
import InvenTree.helpers
|
||||
import InvenTree.ready
|
||||
import InvenTree.tasks
|
||||
|
||||
from plugin.events import trigger_event
|
||||
@ -38,32 +40,6 @@ from stock import models as StockModels
|
||||
from users import models as UserModels
|
||||
|
||||
|
||||
def get_next_build_number():
|
||||
"""Returns the next available BuildOrder reference number."""
|
||||
if Build.objects.count() == 0:
|
||||
return '0001'
|
||||
|
||||
build = Build.objects.exclude(reference=None).last()
|
||||
|
||||
attempts = {build.reference}
|
||||
|
||||
reference = build.reference
|
||||
|
||||
while 1:
|
||||
reference = increment(reference)
|
||||
|
||||
if reference in attempts:
|
||||
# Escape infinite recursion
|
||||
return reference
|
||||
|
||||
if Build.objects.filter(reference=reference).exists():
|
||||
attempts.add(reference)
|
||||
else:
|
||||
break
|
||||
|
||||
return reference
|
||||
|
||||
|
||||
class Build(MPTTModel, ReferenceIndexingMixin):
|
||||
"""A Build object organises the creation of new StockItem objects from other existing StockItem objects.
|
||||
|
||||
@ -89,6 +65,9 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
||||
|
||||
OVERDUE_FILTER = Q(status__in=BuildStatus.ACTIVE_CODES) & ~Q(target_date=None) & Q(target_date__lte=datetime.now().date())
|
||||
|
||||
# Global setting for specifying reference pattern
|
||||
REFERENCE_PATTERN_SETTING = 'BUILDORDER_REFERENCE_PATTERN'
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
"""Return the API URL associated with the BuildOrder model"""
|
||||
@ -106,7 +85,7 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
||||
def api_defaults(cls, request):
|
||||
"""Return default values for this model when issuing an API OPTIONS request."""
|
||||
defaults = {
|
||||
'reference': get_next_build_number(),
|
||||
'reference': generate_next_build_reference(),
|
||||
}
|
||||
|
||||
if request and request.user:
|
||||
@ -116,7 +95,8 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Custom save method for the BuildOrder model"""
|
||||
self.rebuild_reference_field()
|
||||
self.validate_reference_field(self.reference)
|
||||
self.reference_int = self.rebuild_reference_field(self.reference)
|
||||
|
||||
try:
|
||||
super().save(*args, **kwargs)
|
||||
@ -172,9 +152,7 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
||||
|
||||
def __str__(self):
|
||||
"""String representation of a BuildOrder"""
|
||||
prefix = getSetting("BUILDORDER_REFERENCE_PREFIX")
|
||||
|
||||
return f"{prefix}{self.reference}"
|
||||
return self.reference
|
||||
|
||||
def get_absolute_url(self):
|
||||
"""Return the web URL associated with this BuildOrder"""
|
||||
@ -186,9 +164,9 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
||||
blank=False,
|
||||
help_text=_('Build Order Reference'),
|
||||
verbose_name=_('Reference'),
|
||||
default=get_next_build_number,
|
||||
default=generate_next_build_reference,
|
||||
validators=[
|
||||
validate_build_order_reference
|
||||
validate_build_order_reference,
|
||||
]
|
||||
)
|
||||
|
||||
@ -199,7 +177,6 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
||||
help_text=_('Brief description of the build')
|
||||
)
|
||||
|
||||
# TODO - Perhaps delete the build "tree"
|
||||
parent = TreeForeignKey(
|
||||
'self',
|
||||
on_delete=models.SET_NULL,
|
||||
@ -1092,6 +1069,10 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
||||
@receiver(post_save, sender=Build, dispatch_uid='build_post_save_log')
|
||||
def after_save_build(sender, instance: Build, created: bool, **kwargs):
|
||||
"""Callback function to be executed after a Build instance is saved."""
|
||||
# Escape if we are importing data
|
||||
if InvenTree.ready.isImportingData() or not InvenTree.ready.canAppAccessDatabase(allow_test=True):
|
||||
return
|
||||
|
||||
from . import tasks as build_tasks
|
||||
|
||||
if created:
|
||||
|
@ -11,7 +11,7 @@ from rest_framework import serializers
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from InvenTree.serializers import InvenTreeModelSerializer, InvenTreeAttachmentSerializer
|
||||
from InvenTree.serializers import ReferenceIndexingSerializerMixin, UserSerializer
|
||||
from InvenTree.serializers import UserSerializer
|
||||
|
||||
import InvenTree.helpers
|
||||
from InvenTree.helpers import extract_serial_numbers
|
||||
@ -28,7 +28,7 @@ from users.serializers import OwnerSerializer
|
||||
from .models import Build, BuildItem, BuildOrderAttachment
|
||||
|
||||
|
||||
class BuildSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer):
|
||||
class BuildSerializer(InvenTreeModelSerializer):
|
||||
"""Serializes a Build object."""
|
||||
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
@ -74,6 +74,16 @@ class BuildSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer
|
||||
if part_detail is not True:
|
||||
self.fields.pop('part_detail')
|
||||
|
||||
reference = serializers.CharField(required=True)
|
||||
|
||||
def validate_reference(self, reference):
|
||||
"""Custom validation for the Build reference field"""
|
||||
|
||||
# Ensure the reference matches the required pattern
|
||||
Build.validate_reference_field(reference)
|
||||
|
||||
return reference
|
||||
|
||||
class Meta:
|
||||
"""Serializer metaclass"""
|
||||
model = Build
|
||||
|
@ -748,6 +748,7 @@ class BuildListTest(BuildAPITest):
|
||||
|
||||
Build.objects.create(
|
||||
part=part,
|
||||
reference="BO-0006",
|
||||
quantity=10,
|
||||
title='Just some thing',
|
||||
status=BuildStatus.PRODUCTION,
|
||||
@ -773,20 +774,23 @@ class BuildListTest(BuildAPITest):
|
||||
Build.objects.create(
|
||||
part=part,
|
||||
quantity=10,
|
||||
reference=f"build-000{i}",
|
||||
reference=f"BO-{i + 10}",
|
||||
title=f"Sub build {i}",
|
||||
parent=parent
|
||||
)
|
||||
|
||||
# And some sub-sub builds
|
||||
for sub_build in Build.objects.filter(parent=parent):
|
||||
for ii, sub_build in enumerate(Build.objects.filter(parent=parent)):
|
||||
|
||||
for i in range(3):
|
||||
|
||||
x = ii * 10 + i + 50
|
||||
|
||||
Build.objects.create(
|
||||
part=part,
|
||||
reference=f"{sub_build.reference}-00{i}-sub",
|
||||
reference=f"BO-{x}",
|
||||
title=f"{sub_build.reference}-00{i}-sub",
|
||||
quantity=40,
|
||||
title=f"sub sub build {i}",
|
||||
parent=sub_build
|
||||
)
|
||||
|
||||
|
@ -12,7 +12,7 @@ from InvenTree import status_codes as status
|
||||
|
||||
import common.models
|
||||
import build.tasks
|
||||
from build.models import Build, BuildItem, get_next_build_number
|
||||
from build.models import Build, BuildItem, generate_next_build_reference
|
||||
from part.models import Part, BomItem, BomItemSubstitute
|
||||
from stock.models import StockItem
|
||||
from users.models import Owner
|
||||
@ -88,7 +88,7 @@ class BuildTestBase(TestCase):
|
||||
quantity=2
|
||||
)
|
||||
|
||||
ref = get_next_build_number()
|
||||
ref = generate_next_build_reference()
|
||||
|
||||
# Create a "Build" object to make 10x objects
|
||||
self.build = Build.objects.create(
|
||||
@ -133,20 +133,97 @@ class BuildTest(BuildTestBase):
|
||||
def test_ref_int(self):
|
||||
"""Test the "integer reference" field used for natural sorting"""
|
||||
|
||||
for ii in range(10):
|
||||
common.models.InvenTreeSetting.set_setting('BUILDORDER_REFERENCE_PATTERN', 'BO-{ref}-???', change_user=None)
|
||||
|
||||
refs = {
|
||||
'BO-123-456': 123,
|
||||
'BO-456-123': 456,
|
||||
'BO-999-ABC': 999,
|
||||
'BO-123ABC-ABC': 123,
|
||||
'BO-ABC123-ABC': 123,
|
||||
}
|
||||
|
||||
for ref, ref_int in refs.items():
|
||||
build = Build(
|
||||
reference=f"{ii}_abcde",
|
||||
reference=ref,
|
||||
quantity=1,
|
||||
part=self.assembly,
|
||||
title="Making some parts"
|
||||
title='Making some parts',
|
||||
)
|
||||
|
||||
self.assertEqual(build.reference_int, 0)
|
||||
|
||||
build.save()
|
||||
self.assertEqual(build.reference_int, ref_int)
|
||||
|
||||
# After saving, the integer reference should have been updated
|
||||
self.assertEqual(build.reference_int, ii)
|
||||
def test_ref_validation(self):
|
||||
"""Test that the reference field validation works as expected"""
|
||||
|
||||
# Default reference pattern = 'BO-{ref:04d}
|
||||
|
||||
# These patterns should fail
|
||||
for ref in [
|
||||
'BO-1234x',
|
||||
'BO1234',
|
||||
'OB-1234',
|
||||
'BO--1234'
|
||||
]:
|
||||
with self.assertRaises(ValidationError):
|
||||
Build.objects.create(
|
||||
part=self.assembly,
|
||||
quantity=10,
|
||||
reference=ref,
|
||||
title='Invalid reference',
|
||||
)
|
||||
|
||||
for ref in [
|
||||
'BO-1234',
|
||||
'BO-9999',
|
||||
'BO-123'
|
||||
]:
|
||||
Build.objects.create(
|
||||
part=self.assembly,
|
||||
quantity=10,
|
||||
reference=ref,
|
||||
title='Valid reference',
|
||||
)
|
||||
|
||||
# Try a new validator pattern
|
||||
common.models.InvenTreeSetting.set_setting('BUILDORDER_REFERENCE_PATTERN', '{ref}-BO', change_user=None)
|
||||
|
||||
for ref in [
|
||||
'1234-BO',
|
||||
'9999-BO'
|
||||
]:
|
||||
Build.objects.create(
|
||||
part=self.assembly,
|
||||
quantity=10,
|
||||
reference=ref,
|
||||
title='Valid reference',
|
||||
)
|
||||
|
||||
def test_next_ref(self):
|
||||
"""Test that the next reference is automatically generated"""
|
||||
|
||||
common.models.InvenTreeSetting.set_setting('BUILDORDER_REFERENCE_PATTERN', 'XYZ-{ref:06d}', change_user=None)
|
||||
|
||||
build = Build.objects.create(
|
||||
part=self.assembly,
|
||||
quantity=5,
|
||||
reference='XYZ-987',
|
||||
title='Some thing',
|
||||
)
|
||||
|
||||
self.assertEqual(build.reference_int, 987)
|
||||
|
||||
# Now create one *without* specifying the reference
|
||||
build = Build.objects.create(
|
||||
part=self.assembly,
|
||||
quantity=1,
|
||||
title='Some new title',
|
||||
)
|
||||
|
||||
self.assertEqual(build.reference, 'XYZ-000988')
|
||||
self.assertEqual(build.reference_int, 988)
|
||||
|
||||
def test_init(self):
|
||||
"""Perform some basic tests before we start the ball rolling"""
|
||||
@ -404,7 +481,7 @@ class BuildTest(BuildTestBase):
|
||||
"""Test that a notification is sent when a new build is created"""
|
||||
|
||||
Build.objects.create(
|
||||
reference='IIIII',
|
||||
reference='BO-9999',
|
||||
title='Some new build',
|
||||
part=self.assembly,
|
||||
quantity=5,
|
||||
|
@ -104,3 +104,57 @@ class TestReferenceMigration(MigratorTestCase):
|
||||
# Check that the build reference is properly assigned
|
||||
for build in Build.objects.all():
|
||||
self.assertEqual(str(build.reference), str(build.pk))
|
||||
|
||||
|
||||
class TestReferencePatternMigration(MigratorTestCase):
|
||||
"""Unit test for data migration which converts reference to new format.
|
||||
|
||||
Ref: https://github.com/inventree/InvenTree/pull/3267
|
||||
"""
|
||||
|
||||
migrate_from = ('build', '0019_auto_20201019_1302')
|
||||
migrate_to = ('build', helpers.getNewestMigrationFile('build'))
|
||||
|
||||
def prepare(self):
|
||||
"""Create some initial data prior to migration"""
|
||||
|
||||
Setting = self.old_state.apps.get_model('common', 'inventreesetting')
|
||||
|
||||
# Create a custom existing prefix so we can confirm the operation is working
|
||||
Setting.objects.create(
|
||||
key='BUILDORDER_REFERENCE_PREFIX',
|
||||
value='BuildOrder-',
|
||||
)
|
||||
|
||||
Part = self.old_state.apps.get_model('part', 'part')
|
||||
|
||||
assembly = Part.objects.create(
|
||||
name='Assy 1',
|
||||
description='An assembly',
|
||||
level=0, lft=0, rght=0, tree_id=0,
|
||||
)
|
||||
|
||||
Build = self.old_state.apps.get_model('build', 'build')
|
||||
|
||||
for idx in range(1, 11):
|
||||
Build.objects.create(
|
||||
part=assembly,
|
||||
title=f"Build {idx}",
|
||||
quantity=idx,
|
||||
reference=f"{idx + 100}",
|
||||
level=0, lft=0, rght=0, tree_id=0,
|
||||
)
|
||||
|
||||
def test_reference_migration(self):
|
||||
"""Test that the reference fields have been correctly updated"""
|
||||
|
||||
Build = self.new_state.apps.get_model('build', 'build')
|
||||
|
||||
for build in Build.objects.all():
|
||||
self.assertTrue(build.reference.startswith('BuildOrder-'))
|
||||
|
||||
Setting = self.new_state.apps.get_model('common', 'inventreesetting')
|
||||
|
||||
pattern = Setting.objects.get(key='BUILDORDER_REFERENCE_PATTERN')
|
||||
|
||||
self.assertEqual(pattern.value, 'BuildOrder-{ref:04d}')
|
||||
|
@ -35,7 +35,7 @@ class BuildTestSimple(InvenTreeTestCase):
|
||||
self.assertEqual(b.batch, 'B2')
|
||||
self.assertEqual(b.quantity, 21)
|
||||
|
||||
self.assertEqual(str(b), 'BO0002')
|
||||
self.assertEqual(str(b), 'BO-0002')
|
||||
|
||||
def test_url(self):
|
||||
"""Test URL lookup"""
|
||||
@ -75,11 +75,6 @@ class BuildTestSimple(InvenTreeTestCase):
|
||||
self.assertEqual(b1.is_active, True)
|
||||
self.assertEqual(b2.is_active, False)
|
||||
|
||||
def test_required_parts(self):
|
||||
"""Test set of required BOM items for the build"""
|
||||
# TODO: Generate BOM for test part
|
||||
...
|
||||
|
||||
def test_cancel_build(self):
|
||||
"""Test build cancellation function."""
|
||||
build = Build.objects.get(id=1)
|
||||
|
25
InvenTree/build/validators.py
Normal file
25
InvenTree/build/validators.py
Normal file
@ -0,0 +1,25 @@
|
||||
"""Validation methods for the build app"""
|
||||
|
||||
|
||||
def generate_next_build_reference():
|
||||
"""Generate the next available BuildOrder reference"""
|
||||
|
||||
from build.models import Build
|
||||
|
||||
return Build.generate_reference()
|
||||
|
||||
|
||||
def validate_build_order_reference_pattern(pattern):
|
||||
"""Validate the BuildOrder reference 'pattern' setting"""
|
||||
|
||||
from build.models import Build
|
||||
|
||||
Build.validate_reference_pattern(pattern)
|
||||
|
||||
|
||||
def validate_build_order_reference(value):
|
||||
"""Validate that the BuildOrder reference field matches the required pattern"""
|
||||
|
||||
from build.models import Build
|
||||
|
||||
Build.validate_reference_field(value)
|
@ -36,10 +36,12 @@ from djmoney.contrib.exchange.models import convert_money
|
||||
from djmoney.settings import CURRENCY_CHOICES
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
import build.validators
|
||||
import InvenTree.fields
|
||||
import InvenTree.helpers
|
||||
import InvenTree.ready
|
||||
import InvenTree.validators
|
||||
import order.validators
|
||||
|
||||
logger = logging.getLogger('inventree')
|
||||
|
||||
@ -341,6 +343,10 @@ class BaseInvenTreeSetting(models.Model):
|
||||
# Unless otherwise specified, attempt to create the setting
|
||||
create = kwargs.get('create', True)
|
||||
|
||||
# Prevent creation of new settings objects when importing data
|
||||
if InvenTree.ready.isImportingData() or not InvenTree.ready.canAppAccessDatabase(allow_test=True):
|
||||
create = False
|
||||
|
||||
if create:
|
||||
# Attempt to create a new settings object
|
||||
setting = cls(
|
||||
@ -850,6 +856,13 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
||||
'default': False,
|
||||
},
|
||||
|
||||
'INVENTREE_REQUIRE_CONFIRM': {
|
||||
'name': _('Require confirm'),
|
||||
'description': _('Require explicit user confirmation for certain action.'),
|
||||
'validator': bool,
|
||||
'default': True,
|
||||
},
|
||||
|
||||
'BARCODE_ENABLE': {
|
||||
'name': _('Barcode Support'),
|
||||
'description': _('Enable barcode scanner support'),
|
||||
@ -1132,21 +1145,18 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
||||
'validator': bool,
|
||||
},
|
||||
|
||||
'BUILDORDER_REFERENCE_PREFIX': {
|
||||
'name': _('Build Order Reference Prefix'),
|
||||
'description': _('Prefix value for build order reference'),
|
||||
'default': 'BO',
|
||||
'BUILDORDER_REFERENCE_PATTERN': {
|
||||
'name': _('Build Order Reference Pattern'),
|
||||
'description': _('Required pattern for generating Build Order reference field'),
|
||||
'default': 'BO-{ref:04d}',
|
||||
'validator': build.validators.validate_build_order_reference_pattern,
|
||||
},
|
||||
|
||||
'BUILDORDER_REFERENCE_REGEX': {
|
||||
'name': _('Build Order Reference Regex'),
|
||||
'description': _('Regular expression pattern for matching build order reference')
|
||||
},
|
||||
|
||||
'SALESORDER_REFERENCE_PREFIX': {
|
||||
'name': _('Sales Order Reference Prefix'),
|
||||
'description': _('Prefix value for sales order reference'),
|
||||
'default': 'SO',
|
||||
'SALESORDER_REFERENCE_PATTERN': {
|
||||
'name': _('Sales Order Reference Pattern'),
|
||||
'description': _('Required pattern for generating Sales Order reference field'),
|
||||
'default': 'SO-{ref:04d}',
|
||||
'validator': order.validators.validate_sales_order_reference_pattern,
|
||||
},
|
||||
|
||||
'SALESORDER_DEFAULT_SHIPMENT': {
|
||||
@ -1156,10 +1166,11 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
||||
'validator': bool,
|
||||
},
|
||||
|
||||
'PURCHASEORDER_REFERENCE_PREFIX': {
|
||||
'name': _('Purchase Order Reference Prefix'),
|
||||
'description': _('Prefix value for purchase order reference'),
|
||||
'default': 'PO',
|
||||
'PURCHASEORDER_REFERENCE_PATTERN': {
|
||||
'name': _('Purchase Order Reference Pattern'),
|
||||
'description': _('Required pattern for generating Purchase Order reference field'),
|
||||
'default': 'PO-{ref:04d}',
|
||||
'validator': order.validators.validate_purchase_order_reference_pattern,
|
||||
},
|
||||
|
||||
# login / SSO
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-06-28 12:13+0000\n"
|
||||
"POT-Creation-Date: 2022-07-04 02:07+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -141,7 +141,7 @@ msgstr ""
|
||||
|
||||
#: InvenTree/models.py:191 stock/models.py:2096
|
||||
#: templates/js/translated/attachment.js:103
|
||||
#: templates/js/translated/attachment.js:239
|
||||
#: templates/js/translated/attachment.js:240
|
||||
msgid "Attachment"
|
||||
msgstr ""
|
||||
|
||||
@ -165,7 +165,7 @@ msgid "Link to external URL"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/models.py:202 templates/js/translated/attachment.js:104
|
||||
#: templates/js/translated/attachment.js:283
|
||||
#: templates/js/translated/attachment.js:284
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
@ -243,7 +243,7 @@ msgstr ""
|
||||
#: stock/templates/stock/location.html:103
|
||||
#: templates/InvenTree/settings/plugin_settings.html:33
|
||||
#: templates/js/translated/bom.js:553 templates/js/translated/bom.js:845
|
||||
#: templates/js/translated/build.js:2475 templates/js/translated/company.js:397
|
||||
#: templates/js/translated/build.js:2476 templates/js/translated/company.js:397
|
||||
#: templates/js/translated/company.js:653
|
||||
#: templates/js/translated/company.js:944 templates/js/translated/order.js:1656
|
||||
#: templates/js/translated/order.js:1888 templates/js/translated/order.js:2476
|
||||
@ -656,7 +656,7 @@ msgstr ""
|
||||
#: report/templates/report/inventree_build_order_base.html:106
|
||||
#: templates/email/build_order_completed.html:16
|
||||
#: templates/email/overdue_build_order.html:15
|
||||
#: templates/js/translated/build.js:727
|
||||
#: templates/js/translated/build.js:728
|
||||
msgid "Build Order"
|
||||
msgstr ""
|
||||
|
||||
@ -680,7 +680,7 @@ msgstr ""
|
||||
#: report/templates/report/inventree_po_report.html:91
|
||||
#: report/templates/report/inventree_so_report.html:92
|
||||
#: templates/js/translated/bom.js:690 templates/js/translated/bom.js:852
|
||||
#: templates/js/translated/build.js:1776 templates/js/translated/order.js:1919
|
||||
#: templates/js/translated/build.js:1777 templates/js/translated/order.js:1919
|
||||
#: templates/js/translated/order.js:2120 templates/js/translated/order.js:3468
|
||||
#: templates/js/translated/order.js:3976
|
||||
msgid "Reference"
|
||||
@ -719,8 +719,8 @@ msgstr ""
|
||||
#: templates/email/overdue_build_order.html:16
|
||||
#: templates/js/translated/barcode.js:435 templates/js/translated/bom.js:552
|
||||
#: templates/js/translated/bom.js:689 templates/js/translated/bom.js:799
|
||||
#: templates/js/translated/build.js:1153 templates/js/translated/build.js:1646
|
||||
#: templates/js/translated/build.js:2082 templates/js/translated/build.js:2480
|
||||
#: templates/js/translated/build.js:1154 templates/js/translated/build.js:1647
|
||||
#: templates/js/translated/build.js:2083 templates/js/translated/build.js:2481
|
||||
#: templates/js/translated/company.js:255
|
||||
#: templates/js/translated/company.js:484
|
||||
#: templates/js/translated/company.js:594
|
||||
@ -748,8 +748,8 @@ msgstr ""
|
||||
msgid "SalesOrder to which this build is allocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:236 build/serializers.py:758
|
||||
#: templates/js/translated/build.js:2070 templates/js/translated/order.js:2818
|
||||
#: build/models.py:236 build/serializers.py:774
|
||||
#: templates/js/translated/build.js:2071 templates/js/translated/order.js:2818
|
||||
msgid "Source Location"
|
||||
msgstr ""
|
||||
|
||||
@ -812,7 +812,7 @@ msgid "Target date for build completion. Build will be overdue after this date."
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:289 order/models.py:323
|
||||
#: templates/js/translated/build.js:2557
|
||||
#: templates/js/translated/build.js:2558
|
||||
msgid "Completion Date"
|
||||
msgstr ""
|
||||
|
||||
@ -820,7 +820,7 @@ msgstr ""
|
||||
msgid "completed by"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:303 templates/js/translated/build.js:2525
|
||||
#: build/models.py:303 templates/js/translated/build.js:2526
|
||||
msgid "Issued by"
|
||||
msgstr ""
|
||||
|
||||
@ -833,7 +833,7 @@ msgstr ""
|
||||
#: order/templates/order/order_base.html:176
|
||||
#: order/templates/order/sales_order_base.html:183 part/models.py:935
|
||||
#: report/templates/report/inventree_build_order_base.html:159
|
||||
#: templates/js/translated/build.js:2537 templates/js/translated/order.js:1690
|
||||
#: templates/js/translated/build.js:2538 templates/js/translated/order.js:1690
|
||||
msgid "Responsible"
|
||||
msgstr ""
|
||||
|
||||
@ -874,48 +874,48 @@ msgstr ""
|
||||
msgid "Build output does not match Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1148
|
||||
#: build/models.py:1162
|
||||
msgid "Build item must specify a build output, as master part is marked as trackable"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1157
|
||||
#: build/models.py:1171
|
||||
#, python-brace-format
|
||||
msgid "Allocated quantity ({q}) must not exceed available stock quantity ({a})"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1167
|
||||
#: build/models.py:1181
|
||||
msgid "Stock item is over-allocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1173 order/models.py:1404
|
||||
#: build/models.py:1187 order/models.py:1404
|
||||
msgid "Allocation quantity must be greater than zero"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1179
|
||||
#: build/models.py:1193
|
||||
msgid "Quantity must be 1 for serialized stock"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1236
|
||||
#: build/models.py:1250
|
||||
msgid "Selected stock item not found in BOM"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1305 stock/templates/stock/item_base.html:177
|
||||
#: templates/InvenTree/search.html:137 templates/js/translated/build.js:2453
|
||||
#: build/models.py:1319 stock/templates/stock/item_base.html:177
|
||||
#: templates/InvenTree/search.html:137 templates/js/translated/build.js:2454
|
||||
#: templates/navbar.html:38
|
||||
msgid "Build"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1306
|
||||
#: build/models.py:1320
|
||||
msgid "Build to allocate parts"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1322 build/serializers.py:603 order/serializers.py:996
|
||||
#: build/models.py:1336 build/serializers.py:619 order/serializers.py:996
|
||||
#: order/serializers.py:1017 stock/serializers.py:394 stock/serializers.py:741
|
||||
#: stock/serializers.py:867 stock/templates/stock/item_base.html:10
|
||||
#: stock/templates/stock/item_base.html:23
|
||||
#: stock/templates/stock/item_base.html:199
|
||||
#: templates/js/translated/build.js:738 templates/js/translated/build.js:743
|
||||
#: templates/js/translated/build.js:2084 templates/js/translated/build.js:2642
|
||||
#: templates/js/translated/build.js:739 templates/js/translated/build.js:744
|
||||
#: templates/js/translated/build.js:2085 templates/js/translated/build.js:2643
|
||||
#: templates/js/translated/order.js:101 templates/js/translated/order.js:2831
|
||||
#: templates/js/translated/order.js:3135 templates/js/translated/order.js:3140
|
||||
#: templates/js/translated/order.js:3235 templates/js/translated/order.js:3325
|
||||
@ -924,11 +924,11 @@ msgstr ""
|
||||
msgid "Stock Item"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1323
|
||||
#: build/models.py:1337
|
||||
msgid "Source stock item"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1335 build/serializers.py:183
|
||||
#: build/models.py:1349 build/serializers.py:183
|
||||
#: build/templates/build/build_base.html:82
|
||||
#: build/templates/build/detail.html:34 common/models.py:1644
|
||||
#: company/forms.py:36 company/templates/company/supplier_part.html:279
|
||||
@ -947,10 +947,10 @@ msgstr ""
|
||||
#: stock/templates/stock/item_base.html:300
|
||||
#: templates/email/build_order_completed.html:18
|
||||
#: templates/js/translated/barcode.js:437 templates/js/translated/bom.js:691
|
||||
#: templates/js/translated/bom.js:860 templates/js/translated/build.js:422
|
||||
#: templates/js/translated/build.js:574 templates/js/translated/build.js:765
|
||||
#: templates/js/translated/build.js:1175 templates/js/translated/build.js:1672
|
||||
#: templates/js/translated/build.js:2085
|
||||
#: templates/js/translated/bom.js:860 templates/js/translated/build.js:423
|
||||
#: templates/js/translated/build.js:575 templates/js/translated/build.js:766
|
||||
#: templates/js/translated/build.js:1176 templates/js/translated/build.js:1673
|
||||
#: templates/js/translated/build.js:2086
|
||||
#: templates/js/translated/model_renderers.js:120
|
||||
#: templates/js/translated/order.js:117 templates/js/translated/order.js:886
|
||||
#: templates/js/translated/order.js:1925 templates/js/translated/order.js:2126
|
||||
@ -965,20 +965,20 @@ msgstr ""
|
||||
msgid "Quantity"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1336
|
||||
#: build/models.py:1350
|
||||
msgid "Stock quantity to allocate to build"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1344
|
||||
#: build/models.py:1358
|
||||
msgid "Install into"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1345
|
||||
#: build/models.py:1359
|
||||
msgid "Destination stock item"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:128 build/serializers.py:632
|
||||
#: templates/js/translated/build.js:1163
|
||||
#: build/serializers.py:128 build/serializers.py:648
|
||||
#: templates/js/translated/build.js:1164
|
||||
msgid "Build Output"
|
||||
msgstr ""
|
||||
|
||||
@ -1002,7 +1002,7 @@ msgstr ""
|
||||
msgid "Enter quantity for build output"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:198 build/serializers.py:623 order/models.py:355
|
||||
#: build/serializers.py:198 build/serializers.py:639 order/models.py:355
|
||||
#: order/serializers.py:280 order/serializers.py:435 part/serializers.py:503
|
||||
#: part/serializers.py:926 stock/models.py:478 stock/models.py:1244
|
||||
#: stock/serializers.py:300
|
||||
@ -1048,8 +1048,8 @@ msgstr ""
|
||||
#: stock/serializers.py:902 stock/serializers.py:1135
|
||||
#: stock/templates/stock/item_base.html:390
|
||||
#: templates/js/translated/barcode.js:436
|
||||
#: templates/js/translated/barcode.js:618 templates/js/translated/build.js:750
|
||||
#: templates/js/translated/build.js:1684 templates/js/translated/order.js:1213
|
||||
#: templates/js/translated/barcode.js:618 templates/js/translated/build.js:751
|
||||
#: templates/js/translated/build.js:1685 templates/js/translated/order.js:1213
|
||||
#: templates/js/translated/order.js:3147 templates/js/translated/order.js:3250
|
||||
#: templates/js/translated/order.js:3258 templates/js/translated/order.js:3339
|
||||
#: templates/js/translated/part.js:181 templates/js/translated/stock.js:584
|
||||
@ -1065,7 +1065,7 @@ msgstr ""
|
||||
#: build/serializers.py:363 build/templates/build/build_base.html:142
|
||||
#: build/templates/build/detail.html:62 order/models.py:665
|
||||
#: order/serializers.py:458 stock/templates/stock/item_base.html:423
|
||||
#: templates/js/translated/barcode.js:182 templates/js/translated/build.js:2509
|
||||
#: templates/js/translated/barcode.js:182 templates/js/translated/build.js:2510
|
||||
#: templates/js/translated/order.js:1320 templates/js/translated/order.js:1660
|
||||
#: templates/js/translated/order.js:2481 templates/js/translated/stock.js:1825
|
||||
#: templates/js/translated/stock.js:2530 templates/js/translated/stock.js:2662
|
||||
@ -1097,104 +1097,116 @@ msgid "Delete any build outputs which have not been completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:470
|
||||
msgid "Accept Unallocated"
|
||||
msgid "Accept Overallocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:471
|
||||
msgid "Accept that stock items have not been fully allocated to this build order"
|
||||
msgid "Accept stock items which have been overallocated to this build order"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:481 templates/js/translated/build.js:195
|
||||
msgid "Required stock has not been fully allocated"
|
||||
#: build/serializers.py:481
|
||||
msgid "Some stock items have been overallocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:486
|
||||
msgid "Accept Incomplete"
|
||||
msgid "Accept Unallocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:487
|
||||
msgid "Accept that stock items have not been fully allocated to this build order"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:497 templates/js/translated/build.js:196
|
||||
msgid "Required stock has not been fully allocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:502
|
||||
msgid "Accept Incomplete"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:503
|
||||
msgid "Accept that the required number of build outputs have not been completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:497 templates/js/translated/build.js:199
|
||||
#: build/serializers.py:513 templates/js/translated/build.js:200
|
||||
msgid "Required build quantity has not been completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:506
|
||||
#: build/serializers.py:522
|
||||
msgid "Build order has incomplete outputs"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:509 build/templates/build/build_base.html:95
|
||||
#: build/serializers.py:525 build/templates/build/build_base.html:95
|
||||
msgid "No build outputs have been created for this build order"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:535 build/serializers.py:580 part/models.py:2719
|
||||
#: build/serializers.py:551 build/serializers.py:596 part/models.py:2719
|
||||
#: part/models.py:2853
|
||||
msgid "BOM Item"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:545
|
||||
#: build/serializers.py:561
|
||||
msgid "Build output"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:553
|
||||
#: build/serializers.py:569
|
||||
msgid "Build output must point to the same build"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:594
|
||||
#: build/serializers.py:610
|
||||
msgid "bom_item.part must point to the same part as the build order"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:609 stock/serializers.py:754
|
||||
#: build/serializers.py:625 stock/serializers.py:754
|
||||
msgid "Item must be in stock"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:667 order/serializers.py:1054
|
||||
#: build/serializers.py:683 order/serializers.py:1054
|
||||
#, python-brace-format
|
||||
msgid "Available quantity ({q}) exceeded"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:673
|
||||
#: build/serializers.py:689
|
||||
msgid "Build output must be specified for allocation of tracked parts"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:680
|
||||
#: build/serializers.py:696
|
||||
msgid "Build output cannot be specified for allocation of untracked parts"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:685
|
||||
#: build/serializers.py:701
|
||||
msgid "This stock item has already been allocated to this build output"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:708 order/serializers.py:1300
|
||||
#: build/serializers.py:724 order/serializers.py:1300
|
||||
msgid "Allocation items must be provided"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:759
|
||||
#: build/serializers.py:775
|
||||
msgid "Stock location where parts are to be sourced (leave blank to take from any location)"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:767
|
||||
#: build/serializers.py:783
|
||||
msgid "Exclude Location"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:768
|
||||
#: build/serializers.py:784
|
||||
msgid "Exclude stock items from this selected location"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:773
|
||||
#: build/serializers.py:789
|
||||
msgid "Interchangeable Stock"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:774
|
||||
#: build/serializers.py:790
|
||||
msgid "Stock items in multiple locations can be used interchangeably"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:779
|
||||
#: build/serializers.py:795
|
||||
msgid "Substitute Stock"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:780
|
||||
#: build/serializers.py:796
|
||||
msgid "Allow allocation of substitute parts"
|
||||
msgstr ""
|
||||
|
||||
@ -1277,7 +1289,7 @@ msgstr ""
|
||||
#: order/templates/order/order_base.html:162
|
||||
#: order/templates/order/sales_order_base.html:164
|
||||
#: report/templates/report/inventree_build_order_base.html:126
|
||||
#: templates/js/translated/build.js:2549 templates/js/translated/order.js:1677
|
||||
#: templates/js/translated/build.js:2550 templates/js/translated/order.js:1677
|
||||
#: templates/js/translated/order.js:1987 templates/js/translated/order.js:2497
|
||||
#: templates/js/translated/order.js:3537 templates/js/translated/part.js:1040
|
||||
msgid "Target Date"
|
||||
@ -1364,7 +1376,7 @@ msgstr ""
|
||||
|
||||
#: build/templates/build/detail.html:80
|
||||
#: stock/templates/stock/item_base.html:170
|
||||
#: templates/js/translated/build.js:1179
|
||||
#: templates/js/translated/build.js:1180
|
||||
#: templates/js/translated/model_renderers.js:124
|
||||
#: templates/js/translated/stock.js:1022 templates/js/translated/stock.js:1839
|
||||
#: templates/js/translated/stock.js:2669
|
||||
@ -1376,7 +1388,7 @@ msgstr ""
|
||||
#: build/templates/build/detail.html:126
|
||||
#: order/templates/order/order_base.html:149
|
||||
#: order/templates/order/sales_order_base.html:158
|
||||
#: templates/js/translated/build.js:2517
|
||||
#: templates/js/translated/build.js:2518
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
|
||||
@ -1396,7 +1408,7 @@ msgstr ""
|
||||
msgid "Allocate Stock to Build"
|
||||
msgstr ""
|
||||
|
||||
#: build/templates/build/detail.html:176 templates/js/translated/build.js:1898
|
||||
#: build/templates/build/detail.html:176 templates/js/translated/build.js:1899
|
||||
msgid "Unallocate stock"
|
||||
msgstr ""
|
||||
|
||||
@ -2868,8 +2880,8 @@ msgstr ""
|
||||
#: company/models.py:538 company/templates/company/supplier_part.html:94
|
||||
#: templates/email/build_order_required_stock.html:19
|
||||
#: templates/email/low_stock_notification.html:18
|
||||
#: templates/js/translated/bom.js:884 templates/js/translated/build.js:1786
|
||||
#: templates/js/translated/build.js:2649 templates/js/translated/company.js:959
|
||||
#: templates/js/translated/bom.js:884 templates/js/translated/build.js:1787
|
||||
#: templates/js/translated/build.js:2650 templates/js/translated/company.js:959
|
||||
#: templates/js/translated/part.js:596 templates/js/translated/part.js:599
|
||||
#: templates/js/translated/table_filters.js:178
|
||||
msgid "Available"
|
||||
@ -3063,7 +3075,7 @@ msgid "New Sales Order"
|
||||
msgstr ""
|
||||
|
||||
#: company/templates/company/detail.html:168
|
||||
#: templates/js/translated/build.js:1657
|
||||
#: templates/js/translated/build.js:1658
|
||||
msgid "Assigned Stock"
|
||||
msgstr ""
|
||||
|
||||
@ -3985,8 +3997,8 @@ msgstr ""
|
||||
#: part/templates/part/import_wizard/ajax_match_fields.html:64
|
||||
#: part/templates/part/import_wizard/ajax_match_references.html:42
|
||||
#: part/templates/part/import_wizard/match_references.html:49
|
||||
#: templates/js/translated/bom.js:77 templates/js/translated/build.js:427
|
||||
#: templates/js/translated/build.js:579 templates/js/translated/build.js:1971
|
||||
#: templates/js/translated/bom.js:77 templates/js/translated/build.js:428
|
||||
#: templates/js/translated/build.js:580 templates/js/translated/build.js:1972
|
||||
#: templates/js/translated/order.js:833 templates/js/translated/order.js:1265
|
||||
#: templates/js/translated/order.js:2742 templates/js/translated/stock.js:621
|
||||
#: templates/js/translated/stock.js:789
|
||||
@ -4109,7 +4121,7 @@ msgstr ""
|
||||
|
||||
#: order/templates/order/sales_order_detail.html:72
|
||||
#: templates/attachment_table.html:6 templates/js/translated/bom.js:1047
|
||||
#: templates/js/translated/build.js:1879
|
||||
#: templates/js/translated/build.js:1880
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
@ -4167,19 +4179,19 @@ msgstr ""
|
||||
msgid "This option must be selected"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1073
|
||||
#: part/api.py:1074
|
||||
msgid "Must be greater than zero"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1077
|
||||
#: part/api.py:1078
|
||||
msgid "Must be a valid quantity"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1092
|
||||
#: part/api.py:1093
|
||||
msgid "Specify location for initial part stock"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1123 part/api.py:1127 part/api.py:1142 part/api.py:1146
|
||||
#: part/api.py:1124 part/api.py:1128 part/api.py:1143 part/api.py:1147
|
||||
msgid "This field is required"
|
||||
msgstr ""
|
||||
|
||||
@ -5773,8 +5785,8 @@ msgstr ""
|
||||
|
||||
#: report/templates/report/inventree_test_report_base.html:79
|
||||
#: stock/models.py:641 stock/templates/stock/item_base.html:322
|
||||
#: templates/js/translated/build.js:420 templates/js/translated/build.js:572
|
||||
#: templates/js/translated/build.js:1173 templates/js/translated/build.js:1670
|
||||
#: templates/js/translated/build.js:421 templates/js/translated/build.js:573
|
||||
#: templates/js/translated/build.js:1174 templates/js/translated/build.js:1671
|
||||
#: templates/js/translated/model_renderers.js:118
|
||||
#: templates/js/translated/order.js:115 templates/js/translated/order.js:3240
|
||||
#: templates/js/translated/order.js:3329 templates/js/translated/stock.js:486
|
||||
@ -6395,7 +6407,7 @@ msgid "Available Quantity"
|
||||
msgstr ""
|
||||
|
||||
#: stock/templates/stock/item_base.html:394
|
||||
#: templates/js/translated/build.js:1692
|
||||
#: templates/js/translated/build.js:1693
|
||||
msgid "No location set"
|
||||
msgstr ""
|
||||
|
||||
@ -6808,7 +6820,7 @@ msgid "Plugin Settings"
|
||||
msgstr ""
|
||||
|
||||
#: templates/InvenTree/settings/plugin.html:16
|
||||
msgid "Changing the settings below require you to immediatly restart the server. Do not change this while under active usage."
|
||||
msgid "Changing the settings below require you to immediately restart the server. Do not change this while under active usage."
|
||||
msgstr ""
|
||||
|
||||
#: templates/InvenTree/settings/plugin.html:34
|
||||
@ -7689,19 +7701,19 @@ msgstr ""
|
||||
msgid "No attachments found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/attachment.js:218
|
||||
#: templates/js/translated/attachment.js:219
|
||||
msgid "Edit Attachment"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/attachment.js:287
|
||||
#: templates/js/translated/attachment.js:289
|
||||
msgid "Upload Date"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/attachment.js:310
|
||||
#: templates/js/translated/attachment.js:312
|
||||
msgid "Edit attachment"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/attachment.js:319
|
||||
#: templates/js/translated/attachment.js:321
|
||||
msgid "Delete attachment"
|
||||
msgstr ""
|
||||
|
||||
@ -7921,25 +7933,25 @@ msgstr ""
|
||||
msgid "Substitutes Available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:832 templates/js/translated/build.js:1768
|
||||
#: templates/js/translated/bom.js:832 templates/js/translated/build.js:1769
|
||||
msgid "Variant stock allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:900 templates/js/translated/build.js:1813
|
||||
#: templates/js/translated/bom.js:900 templates/js/translated/build.js:1814
|
||||
#: templates/js/translated/order.js:3577
|
||||
msgid "No Stock Available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:904 templates/js/translated/build.js:1817
|
||||
#: templates/js/translated/bom.js:904 templates/js/translated/build.js:1818
|
||||
msgid "Includes variant and substitute stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:906 templates/js/translated/build.js:1819
|
||||
#: templates/js/translated/bom.js:906 templates/js/translated/build.js:1820
|
||||
#: templates/js/translated/part.js:759
|
||||
msgid "Includes variant stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:908 templates/js/translated/build.js:1821
|
||||
#: templates/js/translated/bom.js:908 templates/js/translated/build.js:1822
|
||||
msgid "Includes substitute stock"
|
||||
msgstr ""
|
||||
|
||||
@ -7979,11 +7991,11 @@ msgstr ""
|
||||
msgid "Delete BOM Item"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:1158 templates/js/translated/build.js:1614
|
||||
#: templates/js/translated/bom.js:1158 templates/js/translated/build.js:1615
|
||||
msgid "No BOM items found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:1402 templates/js/translated/build.js:1752
|
||||
#: templates/js/translated/bom.js:1402 templates/js/translated/build.js:1753
|
||||
msgid "Required Part"
|
||||
msgstr ""
|
||||
|
||||
@ -8015,256 +8027,256 @@ msgstr ""
|
||||
msgid "There are incomplete outputs remaining for this build order"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:185
|
||||
#: templates/js/translated/build.js:186
|
||||
msgid "Build order is ready to be completed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:190
|
||||
#: templates/js/translated/build.js:191
|
||||
msgid "Build Order is incomplete"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:218
|
||||
#: templates/js/translated/build.js:219
|
||||
msgid "Complete Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:259 templates/js/translated/stock.js:92
|
||||
#: templates/js/translated/build.js:260 templates/js/translated/stock.js:92
|
||||
#: templates/js/translated/stock.js:210
|
||||
msgid "Next available serial number"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:261 templates/js/translated/stock.js:94
|
||||
#: templates/js/translated/build.js:262 templates/js/translated/stock.js:94
|
||||
#: templates/js/translated/stock.js:212
|
||||
msgid "Latest serial number"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:270
|
||||
#: templates/js/translated/build.js:271
|
||||
msgid "The Bill of Materials contains trackable parts"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:271
|
||||
#: templates/js/translated/build.js:272
|
||||
msgid "Build outputs must be generated individually"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:279
|
||||
#: templates/js/translated/build.js:280
|
||||
msgid "Trackable parts can have serial numbers specified"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:280
|
||||
#: templates/js/translated/build.js:281
|
||||
msgid "Enter serial numbers to generate multiple single build outputs"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:287
|
||||
#: templates/js/translated/build.js:288
|
||||
msgid "Create Build Output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:318
|
||||
#: templates/js/translated/build.js:319
|
||||
msgid "Allocate stock items to this build output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:329
|
||||
#: templates/js/translated/build.js:330
|
||||
msgid "Unallocate stock from build output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:338
|
||||
#: templates/js/translated/build.js:339
|
||||
msgid "Complete build output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:346
|
||||
#: templates/js/translated/build.js:347
|
||||
msgid "Delete build output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:369
|
||||
#: templates/js/translated/build.js:370
|
||||
msgid "Are you sure you wish to unallocate stock items from this build?"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:387
|
||||
#: templates/js/translated/build.js:388
|
||||
msgid "Unallocate Stock Items"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:407 templates/js/translated/build.js:559
|
||||
#: templates/js/translated/build.js:408 templates/js/translated/build.js:560
|
||||
msgid "Select Build Outputs"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:408 templates/js/translated/build.js:560
|
||||
#: templates/js/translated/build.js:409 templates/js/translated/build.js:561
|
||||
msgid "At least one build output must be selected"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:462 templates/js/translated/build.js:614
|
||||
#: templates/js/translated/build.js:463 templates/js/translated/build.js:615
|
||||
msgid "Output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:480
|
||||
#: templates/js/translated/build.js:481
|
||||
msgid "Complete Build Outputs"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:627
|
||||
#: templates/js/translated/build.js:628
|
||||
msgid "Delete Build Outputs"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:716
|
||||
#: templates/js/translated/build.js:717
|
||||
msgid "No build order allocations found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:754
|
||||
#: templates/js/translated/build.js:755
|
||||
msgid "Location not specified"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1133
|
||||
#: templates/js/translated/build.js:1134
|
||||
msgid "No active build outputs found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1202
|
||||
#: templates/js/translated/build.js:1203
|
||||
msgid "Allocated Stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1209
|
||||
#: templates/js/translated/build.js:1210
|
||||
msgid "No tracked BOM items for this build"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1231
|
||||
#: templates/js/translated/build.js:1232
|
||||
msgid "Completed Tests"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1236
|
||||
#: templates/js/translated/build.js:1237
|
||||
msgid "No required tests for this build"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1709 templates/js/translated/build.js:2660
|
||||
#: templates/js/translated/build.js:1710 templates/js/translated/build.js:2661
|
||||
#: templates/js/translated/order.js:3277
|
||||
msgid "Edit stock allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1711 templates/js/translated/build.js:2661
|
||||
#: templates/js/translated/build.js:1712 templates/js/translated/build.js:2662
|
||||
#: templates/js/translated/order.js:3278
|
||||
msgid "Delete stock allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1729
|
||||
#: templates/js/translated/build.js:1730
|
||||
msgid "Edit Allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1739
|
||||
#: templates/js/translated/build.js:1740
|
||||
msgid "Remove Allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1764
|
||||
#: templates/js/translated/build.js:1765
|
||||
msgid "Substitute parts available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1781
|
||||
#: templates/js/translated/build.js:1782
|
||||
msgid "Quantity Per"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1807 templates/js/translated/order.js:3584
|
||||
#: templates/js/translated/build.js:1808 templates/js/translated/order.js:3584
|
||||
msgid "Insufficient stock available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1809 templates/js/translated/order.js:3582
|
||||
#: templates/js/translated/build.js:1810 templates/js/translated/order.js:3582
|
||||
msgid "Sufficient stock available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1838 templates/js/translated/build.js:2083
|
||||
#: templates/js/translated/build.js:2656 templates/js/translated/order.js:3596
|
||||
#: templates/js/translated/build.js:1839 templates/js/translated/build.js:2084
|
||||
#: templates/js/translated/build.js:2657 templates/js/translated/order.js:3596
|
||||
msgid "Allocated"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1886 templates/js/translated/order.js:3676
|
||||
#: templates/js/translated/build.js:1887 templates/js/translated/order.js:3676
|
||||
msgid "Build stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1890 templates/stock_table.html:50
|
||||
#: templates/js/translated/build.js:1891 templates/stock_table.html:50
|
||||
msgid "Order stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1893 templates/js/translated/order.js:3669
|
||||
#: templates/js/translated/build.js:1894 templates/js/translated/order.js:3669
|
||||
msgid "Allocate stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1932 templates/js/translated/label.js:172
|
||||
#: templates/js/translated/build.js:1933 templates/js/translated/label.js:172
|
||||
#: templates/js/translated/order.js:756 templates/js/translated/order.js:2804
|
||||
#: templates/js/translated/report.js:225
|
||||
msgid "Select Parts"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1933 templates/js/translated/order.js:2805
|
||||
#: templates/js/translated/build.js:1934 templates/js/translated/order.js:2805
|
||||
msgid "You must select at least one part to allocate"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1982 templates/js/translated/order.js:2753
|
||||
#: templates/js/translated/build.js:1983 templates/js/translated/order.js:2753
|
||||
msgid "Specify stock allocation quantity"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2056
|
||||
#: templates/js/translated/build.js:2057
|
||||
msgid "All Parts Allocated"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2057
|
||||
#: templates/js/translated/build.js:2058
|
||||
msgid "All selected parts have been fully allocated"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2071 templates/js/translated/order.js:2819
|
||||
#: templates/js/translated/build.js:2072 templates/js/translated/order.js:2819
|
||||
msgid "Select source location (leave blank to take from all locations)"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2099
|
||||
#: templates/js/translated/build.js:2100
|
||||
msgid "Allocate Stock Items to Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2110 templates/js/translated/order.js:2916
|
||||
#: templates/js/translated/build.js:2111 templates/js/translated/order.js:2916
|
||||
msgid "No matching stock locations"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2182 templates/js/translated/order.js:2993
|
||||
#: templates/js/translated/build.js:2183 templates/js/translated/order.js:2993
|
||||
msgid "No matching stock items"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2279
|
||||
#: templates/js/translated/build.js:2280
|
||||
msgid "Automatic Stock Allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2280
|
||||
#: templates/js/translated/build.js:2281
|
||||
msgid "Stock items will be automatically allocated to this build order, according to the provided guidelines"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2282
|
||||
#: templates/js/translated/build.js:2283
|
||||
msgid "If a location is specifed, stock will only be allocated from that location"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2283
|
||||
#: templates/js/translated/build.js:2284
|
||||
msgid "If stock is considered interchangeable, it will be allocated from the first location it is found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2284
|
||||
#: templates/js/translated/build.js:2285
|
||||
msgid "If substitute stock is allowed, it will be used where stock of the primary part cannot be found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2305
|
||||
#: templates/js/translated/build.js:2306
|
||||
msgid "Allocate Stock Items"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2412
|
||||
#: templates/js/translated/build.js:2413
|
||||
msgid "No builds matching query"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2447 templates/js/translated/part.js:1383
|
||||
#: templates/js/translated/build.js:2448 templates/js/translated/part.js:1383
|
||||
#: templates/js/translated/part.js:1850 templates/js/translated/stock.js:1682
|
||||
#: templates/js/translated/stock.js:2340
|
||||
msgid "Select"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2467
|
||||
#: templates/js/translated/build.js:2468
|
||||
msgid "Build order is overdue"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2495
|
||||
#: templates/js/translated/build.js:2496
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2531 templates/js/translated/stock.js:2582
|
||||
#: templates/js/translated/build.js:2532 templates/js/translated/stock.js:2582
|
||||
msgid "No user information"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2637
|
||||
#: templates/js/translated/build.js:2638
|
||||
msgid "No parts allocated for"
|
||||
msgstr ""
|
||||
|
||||
@ -10058,61 +10070,61 @@ msgstr ""
|
||||
msgid "Select File Format"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:534
|
||||
#: templates/js/translated/tables.js:535
|
||||
msgid "Loading data"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:537
|
||||
#: templates/js/translated/tables.js:538
|
||||
msgid "rows per page"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:542
|
||||
#: templates/js/translated/tables.js:543
|
||||
msgid "Showing all rows"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:544
|
||||
#: templates/js/translated/tables.js:545
|
||||
msgid "Showing"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:544
|
||||
#: templates/js/translated/tables.js:545
|
||||
msgid "to"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:544
|
||||
#: templates/js/translated/tables.js:545
|
||||
msgid "of"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:544
|
||||
#: templates/js/translated/tables.js:545
|
||||
msgid "rows"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:548 templates/navbar.html:102
|
||||
#: templates/js/translated/tables.js:549 templates/navbar.html:102
|
||||
#: templates/search.html:8 templates/search_form.html:6
|
||||
#: templates/search_form.html:7
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:551
|
||||
#: templates/js/translated/tables.js:552
|
||||
msgid "No matching results"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:554
|
||||
#: templates/js/translated/tables.js:555
|
||||
msgid "Hide/Show pagination"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:557
|
||||
#: templates/js/translated/tables.js:558
|
||||
msgid "Refresh"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:560
|
||||
#: templates/js/translated/tables.js:561
|
||||
msgid "Toggle"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:563
|
||||
#: templates/js/translated/tables.js:564
|
||||
msgid "Columns"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:566
|
||||
#: templates/js/translated/tables.js:567
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-06-28 12:13+0000\n"
|
||||
"POT-Creation-Date: 2022-07-04 02:07+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -141,7 +141,7 @@ msgstr ""
|
||||
|
||||
#: InvenTree/models.py:191 stock/models.py:2096
|
||||
#: templates/js/translated/attachment.js:103
|
||||
#: templates/js/translated/attachment.js:239
|
||||
#: templates/js/translated/attachment.js:240
|
||||
msgid "Attachment"
|
||||
msgstr ""
|
||||
|
||||
@ -165,7 +165,7 @@ msgid "Link to external URL"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/models.py:202 templates/js/translated/attachment.js:104
|
||||
#: templates/js/translated/attachment.js:283
|
||||
#: templates/js/translated/attachment.js:284
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
@ -243,7 +243,7 @@ msgstr ""
|
||||
#: stock/templates/stock/location.html:103
|
||||
#: templates/InvenTree/settings/plugin_settings.html:33
|
||||
#: templates/js/translated/bom.js:553 templates/js/translated/bom.js:845
|
||||
#: templates/js/translated/build.js:2475 templates/js/translated/company.js:397
|
||||
#: templates/js/translated/build.js:2476 templates/js/translated/company.js:397
|
||||
#: templates/js/translated/company.js:653
|
||||
#: templates/js/translated/company.js:944 templates/js/translated/order.js:1656
|
||||
#: templates/js/translated/order.js:1888 templates/js/translated/order.js:2476
|
||||
@ -656,7 +656,7 @@ msgstr ""
|
||||
#: report/templates/report/inventree_build_order_base.html:106
|
||||
#: templates/email/build_order_completed.html:16
|
||||
#: templates/email/overdue_build_order.html:15
|
||||
#: templates/js/translated/build.js:727
|
||||
#: templates/js/translated/build.js:728
|
||||
msgid "Build Order"
|
||||
msgstr ""
|
||||
|
||||
@ -680,7 +680,7 @@ msgstr ""
|
||||
#: report/templates/report/inventree_po_report.html:91
|
||||
#: report/templates/report/inventree_so_report.html:92
|
||||
#: templates/js/translated/bom.js:690 templates/js/translated/bom.js:852
|
||||
#: templates/js/translated/build.js:1776 templates/js/translated/order.js:1919
|
||||
#: templates/js/translated/build.js:1777 templates/js/translated/order.js:1919
|
||||
#: templates/js/translated/order.js:2120 templates/js/translated/order.js:3468
|
||||
#: templates/js/translated/order.js:3976
|
||||
msgid "Reference"
|
||||
@ -719,8 +719,8 @@ msgstr ""
|
||||
#: templates/email/overdue_build_order.html:16
|
||||
#: templates/js/translated/barcode.js:435 templates/js/translated/bom.js:552
|
||||
#: templates/js/translated/bom.js:689 templates/js/translated/bom.js:799
|
||||
#: templates/js/translated/build.js:1153 templates/js/translated/build.js:1646
|
||||
#: templates/js/translated/build.js:2082 templates/js/translated/build.js:2480
|
||||
#: templates/js/translated/build.js:1154 templates/js/translated/build.js:1647
|
||||
#: templates/js/translated/build.js:2083 templates/js/translated/build.js:2481
|
||||
#: templates/js/translated/company.js:255
|
||||
#: templates/js/translated/company.js:484
|
||||
#: templates/js/translated/company.js:594
|
||||
@ -748,8 +748,8 @@ msgstr ""
|
||||
msgid "SalesOrder to which this build is allocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:236 build/serializers.py:758
|
||||
#: templates/js/translated/build.js:2070 templates/js/translated/order.js:2818
|
||||
#: build/models.py:236 build/serializers.py:774
|
||||
#: templates/js/translated/build.js:2071 templates/js/translated/order.js:2818
|
||||
msgid "Source Location"
|
||||
msgstr ""
|
||||
|
||||
@ -812,7 +812,7 @@ msgid "Target date for build completion. Build will be overdue after this date."
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:289 order/models.py:323
|
||||
#: templates/js/translated/build.js:2557
|
||||
#: templates/js/translated/build.js:2558
|
||||
msgid "Completion Date"
|
||||
msgstr ""
|
||||
|
||||
@ -820,7 +820,7 @@ msgstr ""
|
||||
msgid "completed by"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:303 templates/js/translated/build.js:2525
|
||||
#: build/models.py:303 templates/js/translated/build.js:2526
|
||||
msgid "Issued by"
|
||||
msgstr ""
|
||||
|
||||
@ -833,7 +833,7 @@ msgstr ""
|
||||
#: order/templates/order/order_base.html:176
|
||||
#: order/templates/order/sales_order_base.html:183 part/models.py:935
|
||||
#: report/templates/report/inventree_build_order_base.html:159
|
||||
#: templates/js/translated/build.js:2537 templates/js/translated/order.js:1690
|
||||
#: templates/js/translated/build.js:2538 templates/js/translated/order.js:1690
|
||||
msgid "Responsible"
|
||||
msgstr ""
|
||||
|
||||
@ -874,48 +874,48 @@ msgstr ""
|
||||
msgid "Build output does not match Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1148
|
||||
#: build/models.py:1162
|
||||
msgid "Build item must specify a build output, as master part is marked as trackable"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1157
|
||||
#: build/models.py:1171
|
||||
#, python-brace-format
|
||||
msgid "Allocated quantity ({q}) must not exceed available stock quantity ({a})"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1167
|
||||
#: build/models.py:1181
|
||||
msgid "Stock item is over-allocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1173 order/models.py:1404
|
||||
#: build/models.py:1187 order/models.py:1404
|
||||
msgid "Allocation quantity must be greater than zero"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1179
|
||||
#: build/models.py:1193
|
||||
msgid "Quantity must be 1 for serialized stock"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1236
|
||||
#: build/models.py:1250
|
||||
msgid "Selected stock item not found in BOM"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1305 stock/templates/stock/item_base.html:177
|
||||
#: templates/InvenTree/search.html:137 templates/js/translated/build.js:2453
|
||||
#: build/models.py:1319 stock/templates/stock/item_base.html:177
|
||||
#: templates/InvenTree/search.html:137 templates/js/translated/build.js:2454
|
||||
#: templates/navbar.html:38
|
||||
msgid "Build"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1306
|
||||
#: build/models.py:1320
|
||||
msgid "Build to allocate parts"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1322 build/serializers.py:603 order/serializers.py:996
|
||||
#: build/models.py:1336 build/serializers.py:619 order/serializers.py:996
|
||||
#: order/serializers.py:1017 stock/serializers.py:394 stock/serializers.py:741
|
||||
#: stock/serializers.py:867 stock/templates/stock/item_base.html:10
|
||||
#: stock/templates/stock/item_base.html:23
|
||||
#: stock/templates/stock/item_base.html:199
|
||||
#: templates/js/translated/build.js:738 templates/js/translated/build.js:743
|
||||
#: templates/js/translated/build.js:2084 templates/js/translated/build.js:2642
|
||||
#: templates/js/translated/build.js:739 templates/js/translated/build.js:744
|
||||
#: templates/js/translated/build.js:2085 templates/js/translated/build.js:2643
|
||||
#: templates/js/translated/order.js:101 templates/js/translated/order.js:2831
|
||||
#: templates/js/translated/order.js:3135 templates/js/translated/order.js:3140
|
||||
#: templates/js/translated/order.js:3235 templates/js/translated/order.js:3325
|
||||
@ -924,11 +924,11 @@ msgstr ""
|
||||
msgid "Stock Item"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1323
|
||||
#: build/models.py:1337
|
||||
msgid "Source stock item"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1335 build/serializers.py:183
|
||||
#: build/models.py:1349 build/serializers.py:183
|
||||
#: build/templates/build/build_base.html:82
|
||||
#: build/templates/build/detail.html:34 common/models.py:1644
|
||||
#: company/forms.py:36 company/templates/company/supplier_part.html:279
|
||||
@ -947,10 +947,10 @@ msgstr ""
|
||||
#: stock/templates/stock/item_base.html:300
|
||||
#: templates/email/build_order_completed.html:18
|
||||
#: templates/js/translated/barcode.js:437 templates/js/translated/bom.js:691
|
||||
#: templates/js/translated/bom.js:860 templates/js/translated/build.js:422
|
||||
#: templates/js/translated/build.js:574 templates/js/translated/build.js:765
|
||||
#: templates/js/translated/build.js:1175 templates/js/translated/build.js:1672
|
||||
#: templates/js/translated/build.js:2085
|
||||
#: templates/js/translated/bom.js:860 templates/js/translated/build.js:423
|
||||
#: templates/js/translated/build.js:575 templates/js/translated/build.js:766
|
||||
#: templates/js/translated/build.js:1176 templates/js/translated/build.js:1673
|
||||
#: templates/js/translated/build.js:2086
|
||||
#: templates/js/translated/model_renderers.js:120
|
||||
#: templates/js/translated/order.js:117 templates/js/translated/order.js:886
|
||||
#: templates/js/translated/order.js:1925 templates/js/translated/order.js:2126
|
||||
@ -965,20 +965,20 @@ msgstr ""
|
||||
msgid "Quantity"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1336
|
||||
#: build/models.py:1350
|
||||
msgid "Stock quantity to allocate to build"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1344
|
||||
#: build/models.py:1358
|
||||
msgid "Install into"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1345
|
||||
#: build/models.py:1359
|
||||
msgid "Destination stock item"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:128 build/serializers.py:632
|
||||
#: templates/js/translated/build.js:1163
|
||||
#: build/serializers.py:128 build/serializers.py:648
|
||||
#: templates/js/translated/build.js:1164
|
||||
msgid "Build Output"
|
||||
msgstr ""
|
||||
|
||||
@ -1002,7 +1002,7 @@ msgstr ""
|
||||
msgid "Enter quantity for build output"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:198 build/serializers.py:623 order/models.py:355
|
||||
#: build/serializers.py:198 build/serializers.py:639 order/models.py:355
|
||||
#: order/serializers.py:280 order/serializers.py:435 part/serializers.py:503
|
||||
#: part/serializers.py:926 stock/models.py:478 stock/models.py:1244
|
||||
#: stock/serializers.py:300
|
||||
@ -1048,8 +1048,8 @@ msgstr ""
|
||||
#: stock/serializers.py:902 stock/serializers.py:1135
|
||||
#: stock/templates/stock/item_base.html:390
|
||||
#: templates/js/translated/barcode.js:436
|
||||
#: templates/js/translated/barcode.js:618 templates/js/translated/build.js:750
|
||||
#: templates/js/translated/build.js:1684 templates/js/translated/order.js:1213
|
||||
#: templates/js/translated/barcode.js:618 templates/js/translated/build.js:751
|
||||
#: templates/js/translated/build.js:1685 templates/js/translated/order.js:1213
|
||||
#: templates/js/translated/order.js:3147 templates/js/translated/order.js:3250
|
||||
#: templates/js/translated/order.js:3258 templates/js/translated/order.js:3339
|
||||
#: templates/js/translated/part.js:181 templates/js/translated/stock.js:584
|
||||
@ -1065,7 +1065,7 @@ msgstr ""
|
||||
#: build/serializers.py:363 build/templates/build/build_base.html:142
|
||||
#: build/templates/build/detail.html:62 order/models.py:665
|
||||
#: order/serializers.py:458 stock/templates/stock/item_base.html:423
|
||||
#: templates/js/translated/barcode.js:182 templates/js/translated/build.js:2509
|
||||
#: templates/js/translated/barcode.js:182 templates/js/translated/build.js:2510
|
||||
#: templates/js/translated/order.js:1320 templates/js/translated/order.js:1660
|
||||
#: templates/js/translated/order.js:2481 templates/js/translated/stock.js:1825
|
||||
#: templates/js/translated/stock.js:2530 templates/js/translated/stock.js:2662
|
||||
@ -1097,104 +1097,116 @@ msgid "Delete any build outputs which have not been completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:470
|
||||
msgid "Accept Unallocated"
|
||||
msgid "Accept Overallocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:471
|
||||
msgid "Accept that stock items have not been fully allocated to this build order"
|
||||
msgid "Accept stock items which have been overallocated to this build order"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:481 templates/js/translated/build.js:195
|
||||
msgid "Required stock has not been fully allocated"
|
||||
#: build/serializers.py:481
|
||||
msgid "Some stock items have been overallocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:486
|
||||
msgid "Accept Incomplete"
|
||||
msgid "Accept Unallocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:487
|
||||
msgid "Accept that stock items have not been fully allocated to this build order"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:497 templates/js/translated/build.js:196
|
||||
msgid "Required stock has not been fully allocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:502
|
||||
msgid "Accept Incomplete"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:503
|
||||
msgid "Accept that the required number of build outputs have not been completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:497 templates/js/translated/build.js:199
|
||||
#: build/serializers.py:513 templates/js/translated/build.js:200
|
||||
msgid "Required build quantity has not been completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:506
|
||||
#: build/serializers.py:522
|
||||
msgid "Build order has incomplete outputs"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:509 build/templates/build/build_base.html:95
|
||||
#: build/serializers.py:525 build/templates/build/build_base.html:95
|
||||
msgid "No build outputs have been created for this build order"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:535 build/serializers.py:580 part/models.py:2719
|
||||
#: build/serializers.py:551 build/serializers.py:596 part/models.py:2719
|
||||
#: part/models.py:2853
|
||||
msgid "BOM Item"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:545
|
||||
#: build/serializers.py:561
|
||||
msgid "Build output"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:553
|
||||
#: build/serializers.py:569
|
||||
msgid "Build output must point to the same build"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:594
|
||||
#: build/serializers.py:610
|
||||
msgid "bom_item.part must point to the same part as the build order"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:609 stock/serializers.py:754
|
||||
#: build/serializers.py:625 stock/serializers.py:754
|
||||
msgid "Item must be in stock"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:667 order/serializers.py:1054
|
||||
#: build/serializers.py:683 order/serializers.py:1054
|
||||
#, python-brace-format
|
||||
msgid "Available quantity ({q}) exceeded"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:673
|
||||
#: build/serializers.py:689
|
||||
msgid "Build output must be specified for allocation of tracked parts"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:680
|
||||
#: build/serializers.py:696
|
||||
msgid "Build output cannot be specified for allocation of untracked parts"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:685
|
||||
#: build/serializers.py:701
|
||||
msgid "This stock item has already been allocated to this build output"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:708 order/serializers.py:1300
|
||||
#: build/serializers.py:724 order/serializers.py:1300
|
||||
msgid "Allocation items must be provided"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:759
|
||||
#: build/serializers.py:775
|
||||
msgid "Stock location where parts are to be sourced (leave blank to take from any location)"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:767
|
||||
#: build/serializers.py:783
|
||||
msgid "Exclude Location"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:768
|
||||
#: build/serializers.py:784
|
||||
msgid "Exclude stock items from this selected location"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:773
|
||||
#: build/serializers.py:789
|
||||
msgid "Interchangeable Stock"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:774
|
||||
#: build/serializers.py:790
|
||||
msgid "Stock items in multiple locations can be used interchangeably"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:779
|
||||
#: build/serializers.py:795
|
||||
msgid "Substitute Stock"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:780
|
||||
#: build/serializers.py:796
|
||||
msgid "Allow allocation of substitute parts"
|
||||
msgstr ""
|
||||
|
||||
@ -1277,7 +1289,7 @@ msgstr ""
|
||||
#: order/templates/order/order_base.html:162
|
||||
#: order/templates/order/sales_order_base.html:164
|
||||
#: report/templates/report/inventree_build_order_base.html:126
|
||||
#: templates/js/translated/build.js:2549 templates/js/translated/order.js:1677
|
||||
#: templates/js/translated/build.js:2550 templates/js/translated/order.js:1677
|
||||
#: templates/js/translated/order.js:1987 templates/js/translated/order.js:2497
|
||||
#: templates/js/translated/order.js:3537 templates/js/translated/part.js:1040
|
||||
msgid "Target Date"
|
||||
@ -1364,7 +1376,7 @@ msgstr ""
|
||||
|
||||
#: build/templates/build/detail.html:80
|
||||
#: stock/templates/stock/item_base.html:170
|
||||
#: templates/js/translated/build.js:1179
|
||||
#: templates/js/translated/build.js:1180
|
||||
#: templates/js/translated/model_renderers.js:124
|
||||
#: templates/js/translated/stock.js:1022 templates/js/translated/stock.js:1839
|
||||
#: templates/js/translated/stock.js:2669
|
||||
@ -1376,7 +1388,7 @@ msgstr ""
|
||||
#: build/templates/build/detail.html:126
|
||||
#: order/templates/order/order_base.html:149
|
||||
#: order/templates/order/sales_order_base.html:158
|
||||
#: templates/js/translated/build.js:2517
|
||||
#: templates/js/translated/build.js:2518
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
|
||||
@ -1396,7 +1408,7 @@ msgstr ""
|
||||
msgid "Allocate Stock to Build"
|
||||
msgstr ""
|
||||
|
||||
#: build/templates/build/detail.html:176 templates/js/translated/build.js:1898
|
||||
#: build/templates/build/detail.html:176 templates/js/translated/build.js:1899
|
||||
msgid "Unallocate stock"
|
||||
msgstr ""
|
||||
|
||||
@ -2868,8 +2880,8 @@ msgstr ""
|
||||
#: company/models.py:538 company/templates/company/supplier_part.html:94
|
||||
#: templates/email/build_order_required_stock.html:19
|
||||
#: templates/email/low_stock_notification.html:18
|
||||
#: templates/js/translated/bom.js:884 templates/js/translated/build.js:1786
|
||||
#: templates/js/translated/build.js:2649 templates/js/translated/company.js:959
|
||||
#: templates/js/translated/bom.js:884 templates/js/translated/build.js:1787
|
||||
#: templates/js/translated/build.js:2650 templates/js/translated/company.js:959
|
||||
#: templates/js/translated/part.js:596 templates/js/translated/part.js:599
|
||||
#: templates/js/translated/table_filters.js:178
|
||||
msgid "Available"
|
||||
@ -3063,7 +3075,7 @@ msgid "New Sales Order"
|
||||
msgstr ""
|
||||
|
||||
#: company/templates/company/detail.html:168
|
||||
#: templates/js/translated/build.js:1657
|
||||
#: templates/js/translated/build.js:1658
|
||||
msgid "Assigned Stock"
|
||||
msgstr ""
|
||||
|
||||
@ -3985,8 +3997,8 @@ msgstr ""
|
||||
#: part/templates/part/import_wizard/ajax_match_fields.html:64
|
||||
#: part/templates/part/import_wizard/ajax_match_references.html:42
|
||||
#: part/templates/part/import_wizard/match_references.html:49
|
||||
#: templates/js/translated/bom.js:77 templates/js/translated/build.js:427
|
||||
#: templates/js/translated/build.js:579 templates/js/translated/build.js:1971
|
||||
#: templates/js/translated/bom.js:77 templates/js/translated/build.js:428
|
||||
#: templates/js/translated/build.js:580 templates/js/translated/build.js:1972
|
||||
#: templates/js/translated/order.js:833 templates/js/translated/order.js:1265
|
||||
#: templates/js/translated/order.js:2742 templates/js/translated/stock.js:621
|
||||
#: templates/js/translated/stock.js:789
|
||||
@ -4109,7 +4121,7 @@ msgstr ""
|
||||
|
||||
#: order/templates/order/sales_order_detail.html:72
|
||||
#: templates/attachment_table.html:6 templates/js/translated/bom.js:1047
|
||||
#: templates/js/translated/build.js:1879
|
||||
#: templates/js/translated/build.js:1880
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
@ -4167,19 +4179,19 @@ msgstr ""
|
||||
msgid "This option must be selected"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1073
|
||||
#: part/api.py:1074
|
||||
msgid "Must be greater than zero"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1077
|
||||
#: part/api.py:1078
|
||||
msgid "Must be a valid quantity"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1092
|
||||
#: part/api.py:1093
|
||||
msgid "Specify location for initial part stock"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1123 part/api.py:1127 part/api.py:1142 part/api.py:1146
|
||||
#: part/api.py:1124 part/api.py:1128 part/api.py:1143 part/api.py:1147
|
||||
msgid "This field is required"
|
||||
msgstr ""
|
||||
|
||||
@ -5773,8 +5785,8 @@ msgstr ""
|
||||
|
||||
#: report/templates/report/inventree_test_report_base.html:79
|
||||
#: stock/models.py:641 stock/templates/stock/item_base.html:322
|
||||
#: templates/js/translated/build.js:420 templates/js/translated/build.js:572
|
||||
#: templates/js/translated/build.js:1173 templates/js/translated/build.js:1670
|
||||
#: templates/js/translated/build.js:421 templates/js/translated/build.js:573
|
||||
#: templates/js/translated/build.js:1174 templates/js/translated/build.js:1671
|
||||
#: templates/js/translated/model_renderers.js:118
|
||||
#: templates/js/translated/order.js:115 templates/js/translated/order.js:3240
|
||||
#: templates/js/translated/order.js:3329 templates/js/translated/stock.js:486
|
||||
@ -6395,7 +6407,7 @@ msgid "Available Quantity"
|
||||
msgstr ""
|
||||
|
||||
#: stock/templates/stock/item_base.html:394
|
||||
#: templates/js/translated/build.js:1692
|
||||
#: templates/js/translated/build.js:1693
|
||||
msgid "No location set"
|
||||
msgstr ""
|
||||
|
||||
@ -6808,7 +6820,7 @@ msgid "Plugin Settings"
|
||||
msgstr ""
|
||||
|
||||
#: templates/InvenTree/settings/plugin.html:16
|
||||
msgid "Changing the settings below require you to immediatly restart the server. Do not change this while under active usage."
|
||||
msgid "Changing the settings below require you to immediately restart the server. Do not change this while under active usage."
|
||||
msgstr ""
|
||||
|
||||
#: templates/InvenTree/settings/plugin.html:34
|
||||
@ -7689,19 +7701,19 @@ msgstr ""
|
||||
msgid "No attachments found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/attachment.js:218
|
||||
#: templates/js/translated/attachment.js:219
|
||||
msgid "Edit Attachment"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/attachment.js:287
|
||||
#: templates/js/translated/attachment.js:289
|
||||
msgid "Upload Date"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/attachment.js:310
|
||||
#: templates/js/translated/attachment.js:312
|
||||
msgid "Edit attachment"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/attachment.js:319
|
||||
#: templates/js/translated/attachment.js:321
|
||||
msgid "Delete attachment"
|
||||
msgstr ""
|
||||
|
||||
@ -7921,25 +7933,25 @@ msgstr ""
|
||||
msgid "Substitutes Available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:832 templates/js/translated/build.js:1768
|
||||
#: templates/js/translated/bom.js:832 templates/js/translated/build.js:1769
|
||||
msgid "Variant stock allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:900 templates/js/translated/build.js:1813
|
||||
#: templates/js/translated/bom.js:900 templates/js/translated/build.js:1814
|
||||
#: templates/js/translated/order.js:3577
|
||||
msgid "No Stock Available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:904 templates/js/translated/build.js:1817
|
||||
#: templates/js/translated/bom.js:904 templates/js/translated/build.js:1818
|
||||
msgid "Includes variant and substitute stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:906 templates/js/translated/build.js:1819
|
||||
#: templates/js/translated/bom.js:906 templates/js/translated/build.js:1820
|
||||
#: templates/js/translated/part.js:759
|
||||
msgid "Includes variant stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:908 templates/js/translated/build.js:1821
|
||||
#: templates/js/translated/bom.js:908 templates/js/translated/build.js:1822
|
||||
msgid "Includes substitute stock"
|
||||
msgstr ""
|
||||
|
||||
@ -7979,11 +7991,11 @@ msgstr ""
|
||||
msgid "Delete BOM Item"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:1158 templates/js/translated/build.js:1614
|
||||
#: templates/js/translated/bom.js:1158 templates/js/translated/build.js:1615
|
||||
msgid "No BOM items found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:1402 templates/js/translated/build.js:1752
|
||||
#: templates/js/translated/bom.js:1402 templates/js/translated/build.js:1753
|
||||
msgid "Required Part"
|
||||
msgstr ""
|
||||
|
||||
@ -8015,256 +8027,256 @@ msgstr ""
|
||||
msgid "There are incomplete outputs remaining for this build order"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:185
|
||||
#: templates/js/translated/build.js:186
|
||||
msgid "Build order is ready to be completed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:190
|
||||
#: templates/js/translated/build.js:191
|
||||
msgid "Build Order is incomplete"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:218
|
||||
#: templates/js/translated/build.js:219
|
||||
msgid "Complete Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:259 templates/js/translated/stock.js:92
|
||||
#: templates/js/translated/build.js:260 templates/js/translated/stock.js:92
|
||||
#: templates/js/translated/stock.js:210
|
||||
msgid "Next available serial number"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:261 templates/js/translated/stock.js:94
|
||||
#: templates/js/translated/build.js:262 templates/js/translated/stock.js:94
|
||||
#: templates/js/translated/stock.js:212
|
||||
msgid "Latest serial number"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:270
|
||||
#: templates/js/translated/build.js:271
|
||||
msgid "The Bill of Materials contains trackable parts"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:271
|
||||
#: templates/js/translated/build.js:272
|
||||
msgid "Build outputs must be generated individually"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:279
|
||||
#: templates/js/translated/build.js:280
|
||||
msgid "Trackable parts can have serial numbers specified"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:280
|
||||
#: templates/js/translated/build.js:281
|
||||
msgid "Enter serial numbers to generate multiple single build outputs"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:287
|
||||
#: templates/js/translated/build.js:288
|
||||
msgid "Create Build Output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:318
|
||||
#: templates/js/translated/build.js:319
|
||||
msgid "Allocate stock items to this build output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:329
|
||||
#: templates/js/translated/build.js:330
|
||||
msgid "Unallocate stock from build output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:338
|
||||
#: templates/js/translated/build.js:339
|
||||
msgid "Complete build output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:346
|
||||
#: templates/js/translated/build.js:347
|
||||
msgid "Delete build output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:369
|
||||
#: templates/js/translated/build.js:370
|
||||
msgid "Are you sure you wish to unallocate stock items from this build?"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:387
|
||||
#: templates/js/translated/build.js:388
|
||||
msgid "Unallocate Stock Items"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:407 templates/js/translated/build.js:559
|
||||
#: templates/js/translated/build.js:408 templates/js/translated/build.js:560
|
||||
msgid "Select Build Outputs"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:408 templates/js/translated/build.js:560
|
||||
#: templates/js/translated/build.js:409 templates/js/translated/build.js:561
|
||||
msgid "At least one build output must be selected"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:462 templates/js/translated/build.js:614
|
||||
#: templates/js/translated/build.js:463 templates/js/translated/build.js:615
|
||||
msgid "Output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:480
|
||||
#: templates/js/translated/build.js:481
|
||||
msgid "Complete Build Outputs"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:627
|
||||
#: templates/js/translated/build.js:628
|
||||
msgid "Delete Build Outputs"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:716
|
||||
#: templates/js/translated/build.js:717
|
||||
msgid "No build order allocations found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:754
|
||||
#: templates/js/translated/build.js:755
|
||||
msgid "Location not specified"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1133
|
||||
#: templates/js/translated/build.js:1134
|
||||
msgid "No active build outputs found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1202
|
||||
#: templates/js/translated/build.js:1203
|
||||
msgid "Allocated Stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1209
|
||||
#: templates/js/translated/build.js:1210
|
||||
msgid "No tracked BOM items for this build"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1231
|
||||
#: templates/js/translated/build.js:1232
|
||||
msgid "Completed Tests"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1236
|
||||
#: templates/js/translated/build.js:1237
|
||||
msgid "No required tests for this build"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1709 templates/js/translated/build.js:2660
|
||||
#: templates/js/translated/build.js:1710 templates/js/translated/build.js:2661
|
||||
#: templates/js/translated/order.js:3277
|
||||
msgid "Edit stock allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1711 templates/js/translated/build.js:2661
|
||||
#: templates/js/translated/build.js:1712 templates/js/translated/build.js:2662
|
||||
#: templates/js/translated/order.js:3278
|
||||
msgid "Delete stock allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1729
|
||||
#: templates/js/translated/build.js:1730
|
||||
msgid "Edit Allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1739
|
||||
#: templates/js/translated/build.js:1740
|
||||
msgid "Remove Allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1764
|
||||
#: templates/js/translated/build.js:1765
|
||||
msgid "Substitute parts available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1781
|
||||
#: templates/js/translated/build.js:1782
|
||||
msgid "Quantity Per"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1807 templates/js/translated/order.js:3584
|
||||
#: templates/js/translated/build.js:1808 templates/js/translated/order.js:3584
|
||||
msgid "Insufficient stock available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1809 templates/js/translated/order.js:3582
|
||||
#: templates/js/translated/build.js:1810 templates/js/translated/order.js:3582
|
||||
msgid "Sufficient stock available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1838 templates/js/translated/build.js:2083
|
||||
#: templates/js/translated/build.js:2656 templates/js/translated/order.js:3596
|
||||
#: templates/js/translated/build.js:1839 templates/js/translated/build.js:2084
|
||||
#: templates/js/translated/build.js:2657 templates/js/translated/order.js:3596
|
||||
msgid "Allocated"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1886 templates/js/translated/order.js:3676
|
||||
#: templates/js/translated/build.js:1887 templates/js/translated/order.js:3676
|
||||
msgid "Build stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1890 templates/stock_table.html:50
|
||||
#: templates/js/translated/build.js:1891 templates/stock_table.html:50
|
||||
msgid "Order stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1893 templates/js/translated/order.js:3669
|
||||
#: templates/js/translated/build.js:1894 templates/js/translated/order.js:3669
|
||||
msgid "Allocate stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1932 templates/js/translated/label.js:172
|
||||
#: templates/js/translated/build.js:1933 templates/js/translated/label.js:172
|
||||
#: templates/js/translated/order.js:756 templates/js/translated/order.js:2804
|
||||
#: templates/js/translated/report.js:225
|
||||
msgid "Select Parts"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1933 templates/js/translated/order.js:2805
|
||||
#: templates/js/translated/build.js:1934 templates/js/translated/order.js:2805
|
||||
msgid "You must select at least one part to allocate"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1982 templates/js/translated/order.js:2753
|
||||
#: templates/js/translated/build.js:1983 templates/js/translated/order.js:2753
|
||||
msgid "Specify stock allocation quantity"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2056
|
||||
#: templates/js/translated/build.js:2057
|
||||
msgid "All Parts Allocated"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2057
|
||||
#: templates/js/translated/build.js:2058
|
||||
msgid "All selected parts have been fully allocated"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2071 templates/js/translated/order.js:2819
|
||||
#: templates/js/translated/build.js:2072 templates/js/translated/order.js:2819
|
||||
msgid "Select source location (leave blank to take from all locations)"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2099
|
||||
#: templates/js/translated/build.js:2100
|
||||
msgid "Allocate Stock Items to Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2110 templates/js/translated/order.js:2916
|
||||
#: templates/js/translated/build.js:2111 templates/js/translated/order.js:2916
|
||||
msgid "No matching stock locations"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2182 templates/js/translated/order.js:2993
|
||||
#: templates/js/translated/build.js:2183 templates/js/translated/order.js:2993
|
||||
msgid "No matching stock items"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2279
|
||||
#: templates/js/translated/build.js:2280
|
||||
msgid "Automatic Stock Allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2280
|
||||
#: templates/js/translated/build.js:2281
|
||||
msgid "Stock items will be automatically allocated to this build order, according to the provided guidelines"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2282
|
||||
#: templates/js/translated/build.js:2283
|
||||
msgid "If a location is specifed, stock will only be allocated from that location"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2283
|
||||
#: templates/js/translated/build.js:2284
|
||||
msgid "If stock is considered interchangeable, it will be allocated from the first location it is found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2284
|
||||
#: templates/js/translated/build.js:2285
|
||||
msgid "If substitute stock is allowed, it will be used where stock of the primary part cannot be found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2305
|
||||
#: templates/js/translated/build.js:2306
|
||||
msgid "Allocate Stock Items"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2412
|
||||
#: templates/js/translated/build.js:2413
|
||||
msgid "No builds matching query"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2447 templates/js/translated/part.js:1383
|
||||
#: templates/js/translated/build.js:2448 templates/js/translated/part.js:1383
|
||||
#: templates/js/translated/part.js:1850 templates/js/translated/stock.js:1682
|
||||
#: templates/js/translated/stock.js:2340
|
||||
msgid "Select"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2467
|
||||
#: templates/js/translated/build.js:2468
|
||||
msgid "Build order is overdue"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2495
|
||||
#: templates/js/translated/build.js:2496
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2531 templates/js/translated/stock.js:2582
|
||||
#: templates/js/translated/build.js:2532 templates/js/translated/stock.js:2582
|
||||
msgid "No user information"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2637
|
||||
#: templates/js/translated/build.js:2638
|
||||
msgid "No parts allocated for"
|
||||
msgstr ""
|
||||
|
||||
@ -10058,61 +10070,61 @@ msgstr ""
|
||||
msgid "Select File Format"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:534
|
||||
#: templates/js/translated/tables.js:535
|
||||
msgid "Loading data"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:537
|
||||
#: templates/js/translated/tables.js:538
|
||||
msgid "rows per page"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:542
|
||||
#: templates/js/translated/tables.js:543
|
||||
msgid "Showing all rows"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:544
|
||||
#: templates/js/translated/tables.js:545
|
||||
msgid "Showing"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:544
|
||||
#: templates/js/translated/tables.js:545
|
||||
msgid "to"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:544
|
||||
#: templates/js/translated/tables.js:545
|
||||
msgid "of"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:544
|
||||
#: templates/js/translated/tables.js:545
|
||||
msgid "rows"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:548 templates/navbar.html:102
|
||||
#: templates/js/translated/tables.js:549 templates/navbar.html:102
|
||||
#: templates/search.html:8 templates/search_form.html:6
|
||||
#: templates/search_form.html:7
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:551
|
||||
#: templates/js/translated/tables.js:552
|
||||
msgid "No matching results"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:554
|
||||
#: templates/js/translated/tables.js:555
|
||||
msgid "Hide/Show pagination"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:557
|
||||
#: templates/js/translated/tables.js:558
|
||||
msgid "Refresh"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:560
|
||||
#: templates/js/translated/tables.js:561
|
||||
msgid "Toggle"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:563
|
||||
#: templates/js/translated/tables.js:564
|
||||
msgid "Columns"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:566
|
||||
#: templates/js/translated/tables.js:567
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-06-28 12:13+0000\n"
|
||||
"POT-Creation-Date: 2022-07-04 02:07+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -141,7 +141,7 @@ msgstr ""
|
||||
|
||||
#: InvenTree/models.py:191 stock/models.py:2096
|
||||
#: templates/js/translated/attachment.js:103
|
||||
#: templates/js/translated/attachment.js:239
|
||||
#: templates/js/translated/attachment.js:240
|
||||
msgid "Attachment"
|
||||
msgstr ""
|
||||
|
||||
@ -165,7 +165,7 @@ msgid "Link to external URL"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/models.py:202 templates/js/translated/attachment.js:104
|
||||
#: templates/js/translated/attachment.js:283
|
||||
#: templates/js/translated/attachment.js:284
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
@ -243,7 +243,7 @@ msgstr ""
|
||||
#: stock/templates/stock/location.html:103
|
||||
#: templates/InvenTree/settings/plugin_settings.html:33
|
||||
#: templates/js/translated/bom.js:553 templates/js/translated/bom.js:845
|
||||
#: templates/js/translated/build.js:2475 templates/js/translated/company.js:397
|
||||
#: templates/js/translated/build.js:2476 templates/js/translated/company.js:397
|
||||
#: templates/js/translated/company.js:653
|
||||
#: templates/js/translated/company.js:944 templates/js/translated/order.js:1656
|
||||
#: templates/js/translated/order.js:1888 templates/js/translated/order.js:2476
|
||||
@ -656,7 +656,7 @@ msgstr ""
|
||||
#: report/templates/report/inventree_build_order_base.html:106
|
||||
#: templates/email/build_order_completed.html:16
|
||||
#: templates/email/overdue_build_order.html:15
|
||||
#: templates/js/translated/build.js:727
|
||||
#: templates/js/translated/build.js:728
|
||||
msgid "Build Order"
|
||||
msgstr ""
|
||||
|
||||
@ -680,7 +680,7 @@ msgstr ""
|
||||
#: report/templates/report/inventree_po_report.html:91
|
||||
#: report/templates/report/inventree_so_report.html:92
|
||||
#: templates/js/translated/bom.js:690 templates/js/translated/bom.js:852
|
||||
#: templates/js/translated/build.js:1776 templates/js/translated/order.js:1919
|
||||
#: templates/js/translated/build.js:1777 templates/js/translated/order.js:1919
|
||||
#: templates/js/translated/order.js:2120 templates/js/translated/order.js:3468
|
||||
#: templates/js/translated/order.js:3976
|
||||
msgid "Reference"
|
||||
@ -719,8 +719,8 @@ msgstr ""
|
||||
#: templates/email/overdue_build_order.html:16
|
||||
#: templates/js/translated/barcode.js:435 templates/js/translated/bom.js:552
|
||||
#: templates/js/translated/bom.js:689 templates/js/translated/bom.js:799
|
||||
#: templates/js/translated/build.js:1153 templates/js/translated/build.js:1646
|
||||
#: templates/js/translated/build.js:2082 templates/js/translated/build.js:2480
|
||||
#: templates/js/translated/build.js:1154 templates/js/translated/build.js:1647
|
||||
#: templates/js/translated/build.js:2083 templates/js/translated/build.js:2481
|
||||
#: templates/js/translated/company.js:255
|
||||
#: templates/js/translated/company.js:484
|
||||
#: templates/js/translated/company.js:594
|
||||
@ -748,8 +748,8 @@ msgstr ""
|
||||
msgid "SalesOrder to which this build is allocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:236 build/serializers.py:758
|
||||
#: templates/js/translated/build.js:2070 templates/js/translated/order.js:2818
|
||||
#: build/models.py:236 build/serializers.py:774
|
||||
#: templates/js/translated/build.js:2071 templates/js/translated/order.js:2818
|
||||
msgid "Source Location"
|
||||
msgstr ""
|
||||
|
||||
@ -812,7 +812,7 @@ msgid "Target date for build completion. Build will be overdue after this date."
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:289 order/models.py:323
|
||||
#: templates/js/translated/build.js:2557
|
||||
#: templates/js/translated/build.js:2558
|
||||
msgid "Completion Date"
|
||||
msgstr ""
|
||||
|
||||
@ -820,7 +820,7 @@ msgstr ""
|
||||
msgid "completed by"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:303 templates/js/translated/build.js:2525
|
||||
#: build/models.py:303 templates/js/translated/build.js:2526
|
||||
msgid "Issued by"
|
||||
msgstr ""
|
||||
|
||||
@ -833,7 +833,7 @@ msgstr ""
|
||||
#: order/templates/order/order_base.html:176
|
||||
#: order/templates/order/sales_order_base.html:183 part/models.py:935
|
||||
#: report/templates/report/inventree_build_order_base.html:159
|
||||
#: templates/js/translated/build.js:2537 templates/js/translated/order.js:1690
|
||||
#: templates/js/translated/build.js:2538 templates/js/translated/order.js:1690
|
||||
msgid "Responsible"
|
||||
msgstr ""
|
||||
|
||||
@ -874,48 +874,48 @@ msgstr ""
|
||||
msgid "Build output does not match Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1148
|
||||
#: build/models.py:1162
|
||||
msgid "Build item must specify a build output, as master part is marked as trackable"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1157
|
||||
#: build/models.py:1171
|
||||
#, python-brace-format
|
||||
msgid "Allocated quantity ({q}) must not exceed available stock quantity ({a})"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1167
|
||||
#: build/models.py:1181
|
||||
msgid "Stock item is over-allocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1173 order/models.py:1404
|
||||
#: build/models.py:1187 order/models.py:1404
|
||||
msgid "Allocation quantity must be greater than zero"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1179
|
||||
#: build/models.py:1193
|
||||
msgid "Quantity must be 1 for serialized stock"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1236
|
||||
#: build/models.py:1250
|
||||
msgid "Selected stock item not found in BOM"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1305 stock/templates/stock/item_base.html:177
|
||||
#: templates/InvenTree/search.html:137 templates/js/translated/build.js:2453
|
||||
#: build/models.py:1319 stock/templates/stock/item_base.html:177
|
||||
#: templates/InvenTree/search.html:137 templates/js/translated/build.js:2454
|
||||
#: templates/navbar.html:38
|
||||
msgid "Build"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1306
|
||||
#: build/models.py:1320
|
||||
msgid "Build to allocate parts"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1322 build/serializers.py:603 order/serializers.py:996
|
||||
#: build/models.py:1336 build/serializers.py:619 order/serializers.py:996
|
||||
#: order/serializers.py:1017 stock/serializers.py:394 stock/serializers.py:741
|
||||
#: stock/serializers.py:867 stock/templates/stock/item_base.html:10
|
||||
#: stock/templates/stock/item_base.html:23
|
||||
#: stock/templates/stock/item_base.html:199
|
||||
#: templates/js/translated/build.js:738 templates/js/translated/build.js:743
|
||||
#: templates/js/translated/build.js:2084 templates/js/translated/build.js:2642
|
||||
#: templates/js/translated/build.js:739 templates/js/translated/build.js:744
|
||||
#: templates/js/translated/build.js:2085 templates/js/translated/build.js:2643
|
||||
#: templates/js/translated/order.js:101 templates/js/translated/order.js:2831
|
||||
#: templates/js/translated/order.js:3135 templates/js/translated/order.js:3140
|
||||
#: templates/js/translated/order.js:3235 templates/js/translated/order.js:3325
|
||||
@ -924,11 +924,11 @@ msgstr ""
|
||||
msgid "Stock Item"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1323
|
||||
#: build/models.py:1337
|
||||
msgid "Source stock item"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1335 build/serializers.py:183
|
||||
#: build/models.py:1349 build/serializers.py:183
|
||||
#: build/templates/build/build_base.html:82
|
||||
#: build/templates/build/detail.html:34 common/models.py:1644
|
||||
#: company/forms.py:36 company/templates/company/supplier_part.html:279
|
||||
@ -947,10 +947,10 @@ msgstr ""
|
||||
#: stock/templates/stock/item_base.html:300
|
||||
#: templates/email/build_order_completed.html:18
|
||||
#: templates/js/translated/barcode.js:437 templates/js/translated/bom.js:691
|
||||
#: templates/js/translated/bom.js:860 templates/js/translated/build.js:422
|
||||
#: templates/js/translated/build.js:574 templates/js/translated/build.js:765
|
||||
#: templates/js/translated/build.js:1175 templates/js/translated/build.js:1672
|
||||
#: templates/js/translated/build.js:2085
|
||||
#: templates/js/translated/bom.js:860 templates/js/translated/build.js:423
|
||||
#: templates/js/translated/build.js:575 templates/js/translated/build.js:766
|
||||
#: templates/js/translated/build.js:1176 templates/js/translated/build.js:1673
|
||||
#: templates/js/translated/build.js:2086
|
||||
#: templates/js/translated/model_renderers.js:120
|
||||
#: templates/js/translated/order.js:117 templates/js/translated/order.js:886
|
||||
#: templates/js/translated/order.js:1925 templates/js/translated/order.js:2126
|
||||
@ -965,20 +965,20 @@ msgstr ""
|
||||
msgid "Quantity"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1336
|
||||
#: build/models.py:1350
|
||||
msgid "Stock quantity to allocate to build"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1344
|
||||
#: build/models.py:1358
|
||||
msgid "Install into"
|
||||
msgstr ""
|
||||
|
||||
#: build/models.py:1345
|
||||
#: build/models.py:1359
|
||||
msgid "Destination stock item"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:128 build/serializers.py:632
|
||||
#: templates/js/translated/build.js:1163
|
||||
#: build/serializers.py:128 build/serializers.py:648
|
||||
#: templates/js/translated/build.js:1164
|
||||
msgid "Build Output"
|
||||
msgstr ""
|
||||
|
||||
@ -1002,7 +1002,7 @@ msgstr ""
|
||||
msgid "Enter quantity for build output"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:198 build/serializers.py:623 order/models.py:355
|
||||
#: build/serializers.py:198 build/serializers.py:639 order/models.py:355
|
||||
#: order/serializers.py:280 order/serializers.py:435 part/serializers.py:503
|
||||
#: part/serializers.py:926 stock/models.py:478 stock/models.py:1244
|
||||
#: stock/serializers.py:300
|
||||
@ -1048,8 +1048,8 @@ msgstr ""
|
||||
#: stock/serializers.py:902 stock/serializers.py:1135
|
||||
#: stock/templates/stock/item_base.html:390
|
||||
#: templates/js/translated/barcode.js:436
|
||||
#: templates/js/translated/barcode.js:618 templates/js/translated/build.js:750
|
||||
#: templates/js/translated/build.js:1684 templates/js/translated/order.js:1213
|
||||
#: templates/js/translated/barcode.js:618 templates/js/translated/build.js:751
|
||||
#: templates/js/translated/build.js:1685 templates/js/translated/order.js:1213
|
||||
#: templates/js/translated/order.js:3147 templates/js/translated/order.js:3250
|
||||
#: templates/js/translated/order.js:3258 templates/js/translated/order.js:3339
|
||||
#: templates/js/translated/part.js:181 templates/js/translated/stock.js:584
|
||||
@ -1065,7 +1065,7 @@ msgstr ""
|
||||
#: build/serializers.py:363 build/templates/build/build_base.html:142
|
||||
#: build/templates/build/detail.html:62 order/models.py:665
|
||||
#: order/serializers.py:458 stock/templates/stock/item_base.html:423
|
||||
#: templates/js/translated/barcode.js:182 templates/js/translated/build.js:2509
|
||||
#: templates/js/translated/barcode.js:182 templates/js/translated/build.js:2510
|
||||
#: templates/js/translated/order.js:1320 templates/js/translated/order.js:1660
|
||||
#: templates/js/translated/order.js:2481 templates/js/translated/stock.js:1825
|
||||
#: templates/js/translated/stock.js:2530 templates/js/translated/stock.js:2662
|
||||
@ -1097,104 +1097,116 @@ msgid "Delete any build outputs which have not been completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:470
|
||||
msgid "Accept Unallocated"
|
||||
msgid "Accept Overallocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:471
|
||||
msgid "Accept that stock items have not been fully allocated to this build order"
|
||||
msgid "Accept stock items which have been overallocated to this build order"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:481 templates/js/translated/build.js:195
|
||||
msgid "Required stock has not been fully allocated"
|
||||
#: build/serializers.py:481
|
||||
msgid "Some stock items have been overallocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:486
|
||||
msgid "Accept Incomplete"
|
||||
msgid "Accept Unallocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:487
|
||||
msgid "Accept that stock items have not been fully allocated to this build order"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:497 templates/js/translated/build.js:196
|
||||
msgid "Required stock has not been fully allocated"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:502
|
||||
msgid "Accept Incomplete"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:503
|
||||
msgid "Accept that the required number of build outputs have not been completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:497 templates/js/translated/build.js:199
|
||||
#: build/serializers.py:513 templates/js/translated/build.js:200
|
||||
msgid "Required build quantity has not been completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:506
|
||||
#: build/serializers.py:522
|
||||
msgid "Build order has incomplete outputs"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:509 build/templates/build/build_base.html:95
|
||||
#: build/serializers.py:525 build/templates/build/build_base.html:95
|
||||
msgid "No build outputs have been created for this build order"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:535 build/serializers.py:580 part/models.py:2719
|
||||
#: build/serializers.py:551 build/serializers.py:596 part/models.py:2719
|
||||
#: part/models.py:2853
|
||||
msgid "BOM Item"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:545
|
||||
#: build/serializers.py:561
|
||||
msgid "Build output"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:553
|
||||
#: build/serializers.py:569
|
||||
msgid "Build output must point to the same build"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:594
|
||||
#: build/serializers.py:610
|
||||
msgid "bom_item.part must point to the same part as the build order"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:609 stock/serializers.py:754
|
||||
#: build/serializers.py:625 stock/serializers.py:754
|
||||
msgid "Item must be in stock"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:667 order/serializers.py:1054
|
||||
#: build/serializers.py:683 order/serializers.py:1054
|
||||
#, python-brace-format
|
||||
msgid "Available quantity ({q}) exceeded"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:673
|
||||
#: build/serializers.py:689
|
||||
msgid "Build output must be specified for allocation of tracked parts"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:680
|
||||
#: build/serializers.py:696
|
||||
msgid "Build output cannot be specified for allocation of untracked parts"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:685
|
||||
#: build/serializers.py:701
|
||||
msgid "This stock item has already been allocated to this build output"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:708 order/serializers.py:1300
|
||||
#: build/serializers.py:724 order/serializers.py:1300
|
||||
msgid "Allocation items must be provided"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:759
|
||||
#: build/serializers.py:775
|
||||
msgid "Stock location where parts are to be sourced (leave blank to take from any location)"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:767
|
||||
#: build/serializers.py:783
|
||||
msgid "Exclude Location"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:768
|
||||
#: build/serializers.py:784
|
||||
msgid "Exclude stock items from this selected location"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:773
|
||||
#: build/serializers.py:789
|
||||
msgid "Interchangeable Stock"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:774
|
||||
#: build/serializers.py:790
|
||||
msgid "Stock items in multiple locations can be used interchangeably"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:779
|
||||
#: build/serializers.py:795
|
||||
msgid "Substitute Stock"
|
||||
msgstr ""
|
||||
|
||||
#: build/serializers.py:780
|
||||
#: build/serializers.py:796
|
||||
msgid "Allow allocation of substitute parts"
|
||||
msgstr ""
|
||||
|
||||
@ -1277,7 +1289,7 @@ msgstr ""
|
||||
#: order/templates/order/order_base.html:162
|
||||
#: order/templates/order/sales_order_base.html:164
|
||||
#: report/templates/report/inventree_build_order_base.html:126
|
||||
#: templates/js/translated/build.js:2549 templates/js/translated/order.js:1677
|
||||
#: templates/js/translated/build.js:2550 templates/js/translated/order.js:1677
|
||||
#: templates/js/translated/order.js:1987 templates/js/translated/order.js:2497
|
||||
#: templates/js/translated/order.js:3537 templates/js/translated/part.js:1040
|
||||
msgid "Target Date"
|
||||
@ -1364,7 +1376,7 @@ msgstr ""
|
||||
|
||||
#: build/templates/build/detail.html:80
|
||||
#: stock/templates/stock/item_base.html:170
|
||||
#: templates/js/translated/build.js:1179
|
||||
#: templates/js/translated/build.js:1180
|
||||
#: templates/js/translated/model_renderers.js:124
|
||||
#: templates/js/translated/stock.js:1022 templates/js/translated/stock.js:1839
|
||||
#: templates/js/translated/stock.js:2669
|
||||
@ -1376,7 +1388,7 @@ msgstr ""
|
||||
#: build/templates/build/detail.html:126
|
||||
#: order/templates/order/order_base.html:149
|
||||
#: order/templates/order/sales_order_base.html:158
|
||||
#: templates/js/translated/build.js:2517
|
||||
#: templates/js/translated/build.js:2518
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
|
||||
@ -1396,7 +1408,7 @@ msgstr ""
|
||||
msgid "Allocate Stock to Build"
|
||||
msgstr ""
|
||||
|
||||
#: build/templates/build/detail.html:176 templates/js/translated/build.js:1898
|
||||
#: build/templates/build/detail.html:176 templates/js/translated/build.js:1899
|
||||
msgid "Unallocate stock"
|
||||
msgstr ""
|
||||
|
||||
@ -2868,8 +2880,8 @@ msgstr ""
|
||||
#: company/models.py:538 company/templates/company/supplier_part.html:94
|
||||
#: templates/email/build_order_required_stock.html:19
|
||||
#: templates/email/low_stock_notification.html:18
|
||||
#: templates/js/translated/bom.js:884 templates/js/translated/build.js:1786
|
||||
#: templates/js/translated/build.js:2649 templates/js/translated/company.js:959
|
||||
#: templates/js/translated/bom.js:884 templates/js/translated/build.js:1787
|
||||
#: templates/js/translated/build.js:2650 templates/js/translated/company.js:959
|
||||
#: templates/js/translated/part.js:596 templates/js/translated/part.js:599
|
||||
#: templates/js/translated/table_filters.js:178
|
||||
msgid "Available"
|
||||
@ -3063,7 +3075,7 @@ msgid "New Sales Order"
|
||||
msgstr ""
|
||||
|
||||
#: company/templates/company/detail.html:168
|
||||
#: templates/js/translated/build.js:1657
|
||||
#: templates/js/translated/build.js:1658
|
||||
msgid "Assigned Stock"
|
||||
msgstr ""
|
||||
|
||||
@ -3985,8 +3997,8 @@ msgstr ""
|
||||
#: part/templates/part/import_wizard/ajax_match_fields.html:64
|
||||
#: part/templates/part/import_wizard/ajax_match_references.html:42
|
||||
#: part/templates/part/import_wizard/match_references.html:49
|
||||
#: templates/js/translated/bom.js:77 templates/js/translated/build.js:427
|
||||
#: templates/js/translated/build.js:579 templates/js/translated/build.js:1971
|
||||
#: templates/js/translated/bom.js:77 templates/js/translated/build.js:428
|
||||
#: templates/js/translated/build.js:580 templates/js/translated/build.js:1972
|
||||
#: templates/js/translated/order.js:833 templates/js/translated/order.js:1265
|
||||
#: templates/js/translated/order.js:2742 templates/js/translated/stock.js:621
|
||||
#: templates/js/translated/stock.js:789
|
||||
@ -4109,7 +4121,7 @@ msgstr ""
|
||||
|
||||
#: order/templates/order/sales_order_detail.html:72
|
||||
#: templates/attachment_table.html:6 templates/js/translated/bom.js:1047
|
||||
#: templates/js/translated/build.js:1879
|
||||
#: templates/js/translated/build.js:1880
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
@ -4167,19 +4179,19 @@ msgstr ""
|
||||
msgid "This option must be selected"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1073
|
||||
#: part/api.py:1074
|
||||
msgid "Must be greater than zero"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1077
|
||||
#: part/api.py:1078
|
||||
msgid "Must be a valid quantity"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1092
|
||||
#: part/api.py:1093
|
||||
msgid "Specify location for initial part stock"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1123 part/api.py:1127 part/api.py:1142 part/api.py:1146
|
||||
#: part/api.py:1124 part/api.py:1128 part/api.py:1143 part/api.py:1147
|
||||
msgid "This field is required"
|
||||
msgstr ""
|
||||
|
||||
@ -5773,8 +5785,8 @@ msgstr ""
|
||||
|
||||
#: report/templates/report/inventree_test_report_base.html:79
|
||||
#: stock/models.py:641 stock/templates/stock/item_base.html:322
|
||||
#: templates/js/translated/build.js:420 templates/js/translated/build.js:572
|
||||
#: templates/js/translated/build.js:1173 templates/js/translated/build.js:1670
|
||||
#: templates/js/translated/build.js:421 templates/js/translated/build.js:573
|
||||
#: templates/js/translated/build.js:1174 templates/js/translated/build.js:1671
|
||||
#: templates/js/translated/model_renderers.js:118
|
||||
#: templates/js/translated/order.js:115 templates/js/translated/order.js:3240
|
||||
#: templates/js/translated/order.js:3329 templates/js/translated/stock.js:486
|
||||
@ -6395,7 +6407,7 @@ msgid "Available Quantity"
|
||||
msgstr ""
|
||||
|
||||
#: stock/templates/stock/item_base.html:394
|
||||
#: templates/js/translated/build.js:1692
|
||||
#: templates/js/translated/build.js:1693
|
||||
msgid "No location set"
|
||||
msgstr ""
|
||||
|
||||
@ -6808,7 +6820,7 @@ msgid "Plugin Settings"
|
||||
msgstr ""
|
||||
|
||||
#: templates/InvenTree/settings/plugin.html:16
|
||||
msgid "Changing the settings below require you to immediatly restart the server. Do not change this while under active usage."
|
||||
msgid "Changing the settings below require you to immediately restart the server. Do not change this while under active usage."
|
||||
msgstr ""
|
||||
|
||||
#: templates/InvenTree/settings/plugin.html:34
|
||||
@ -7689,19 +7701,19 @@ msgstr ""
|
||||
msgid "No attachments found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/attachment.js:218
|
||||
#: templates/js/translated/attachment.js:219
|
||||
msgid "Edit Attachment"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/attachment.js:287
|
||||
#: templates/js/translated/attachment.js:289
|
||||
msgid "Upload Date"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/attachment.js:310
|
||||
#: templates/js/translated/attachment.js:312
|
||||
msgid "Edit attachment"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/attachment.js:319
|
||||
#: templates/js/translated/attachment.js:321
|
||||
msgid "Delete attachment"
|
||||
msgstr ""
|
||||
|
||||
@ -7921,25 +7933,25 @@ msgstr ""
|
||||
msgid "Substitutes Available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:832 templates/js/translated/build.js:1768
|
||||
#: templates/js/translated/bom.js:832 templates/js/translated/build.js:1769
|
||||
msgid "Variant stock allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:900 templates/js/translated/build.js:1813
|
||||
#: templates/js/translated/bom.js:900 templates/js/translated/build.js:1814
|
||||
#: templates/js/translated/order.js:3577
|
||||
msgid "No Stock Available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:904 templates/js/translated/build.js:1817
|
||||
#: templates/js/translated/bom.js:904 templates/js/translated/build.js:1818
|
||||
msgid "Includes variant and substitute stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:906 templates/js/translated/build.js:1819
|
||||
#: templates/js/translated/bom.js:906 templates/js/translated/build.js:1820
|
||||
#: templates/js/translated/part.js:759
|
||||
msgid "Includes variant stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:908 templates/js/translated/build.js:1821
|
||||
#: templates/js/translated/bom.js:908 templates/js/translated/build.js:1822
|
||||
msgid "Includes substitute stock"
|
||||
msgstr ""
|
||||
|
||||
@ -7979,11 +7991,11 @@ msgstr ""
|
||||
msgid "Delete BOM Item"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:1158 templates/js/translated/build.js:1614
|
||||
#: templates/js/translated/bom.js:1158 templates/js/translated/build.js:1615
|
||||
msgid "No BOM items found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/bom.js:1402 templates/js/translated/build.js:1752
|
||||
#: templates/js/translated/bom.js:1402 templates/js/translated/build.js:1753
|
||||
msgid "Required Part"
|
||||
msgstr ""
|
||||
|
||||
@ -8015,256 +8027,256 @@ msgstr ""
|
||||
msgid "There are incomplete outputs remaining for this build order"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:185
|
||||
#: templates/js/translated/build.js:186
|
||||
msgid "Build order is ready to be completed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:190
|
||||
#: templates/js/translated/build.js:191
|
||||
msgid "Build Order is incomplete"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:218
|
||||
#: templates/js/translated/build.js:219
|
||||
msgid "Complete Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:259 templates/js/translated/stock.js:92
|
||||
#: templates/js/translated/build.js:260 templates/js/translated/stock.js:92
|
||||
#: templates/js/translated/stock.js:210
|
||||
msgid "Next available serial number"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:261 templates/js/translated/stock.js:94
|
||||
#: templates/js/translated/build.js:262 templates/js/translated/stock.js:94
|
||||
#: templates/js/translated/stock.js:212
|
||||
msgid "Latest serial number"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:270
|
||||
#: templates/js/translated/build.js:271
|
||||
msgid "The Bill of Materials contains trackable parts"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:271
|
||||
#: templates/js/translated/build.js:272
|
||||
msgid "Build outputs must be generated individually"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:279
|
||||
#: templates/js/translated/build.js:280
|
||||
msgid "Trackable parts can have serial numbers specified"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:280
|
||||
#: templates/js/translated/build.js:281
|
||||
msgid "Enter serial numbers to generate multiple single build outputs"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:287
|
||||
#: templates/js/translated/build.js:288
|
||||
msgid "Create Build Output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:318
|
||||
#: templates/js/translated/build.js:319
|
||||
msgid "Allocate stock items to this build output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:329
|
||||
#: templates/js/translated/build.js:330
|
||||
msgid "Unallocate stock from build output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:338
|
||||
#: templates/js/translated/build.js:339
|
||||
msgid "Complete build output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:346
|
||||
#: templates/js/translated/build.js:347
|
||||
msgid "Delete build output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:369
|
||||
#: templates/js/translated/build.js:370
|
||||
msgid "Are you sure you wish to unallocate stock items from this build?"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:387
|
||||
#: templates/js/translated/build.js:388
|
||||
msgid "Unallocate Stock Items"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:407 templates/js/translated/build.js:559
|
||||
#: templates/js/translated/build.js:408 templates/js/translated/build.js:560
|
||||
msgid "Select Build Outputs"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:408 templates/js/translated/build.js:560
|
||||
#: templates/js/translated/build.js:409 templates/js/translated/build.js:561
|
||||
msgid "At least one build output must be selected"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:462 templates/js/translated/build.js:614
|
||||
#: templates/js/translated/build.js:463 templates/js/translated/build.js:615
|
||||
msgid "Output"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:480
|
||||
#: templates/js/translated/build.js:481
|
||||
msgid "Complete Build Outputs"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:627
|
||||
#: templates/js/translated/build.js:628
|
||||
msgid "Delete Build Outputs"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:716
|
||||
#: templates/js/translated/build.js:717
|
||||
msgid "No build order allocations found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:754
|
||||
#: templates/js/translated/build.js:755
|
||||
msgid "Location not specified"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1133
|
||||
#: templates/js/translated/build.js:1134
|
||||
msgid "No active build outputs found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1202
|
||||
#: templates/js/translated/build.js:1203
|
||||
msgid "Allocated Stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1209
|
||||
#: templates/js/translated/build.js:1210
|
||||
msgid "No tracked BOM items for this build"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1231
|
||||
#: templates/js/translated/build.js:1232
|
||||
msgid "Completed Tests"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1236
|
||||
#: templates/js/translated/build.js:1237
|
||||
msgid "No required tests for this build"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1709 templates/js/translated/build.js:2660
|
||||
#: templates/js/translated/build.js:1710 templates/js/translated/build.js:2661
|
||||
#: templates/js/translated/order.js:3277
|
||||
msgid "Edit stock allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1711 templates/js/translated/build.js:2661
|
||||
#: templates/js/translated/build.js:1712 templates/js/translated/build.js:2662
|
||||
#: templates/js/translated/order.js:3278
|
||||
msgid "Delete stock allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1729
|
||||
#: templates/js/translated/build.js:1730
|
||||
msgid "Edit Allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1739
|
||||
#: templates/js/translated/build.js:1740
|
||||
msgid "Remove Allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1764
|
||||
#: templates/js/translated/build.js:1765
|
||||
msgid "Substitute parts available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1781
|
||||
#: templates/js/translated/build.js:1782
|
||||
msgid "Quantity Per"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1807 templates/js/translated/order.js:3584
|
||||
#: templates/js/translated/build.js:1808 templates/js/translated/order.js:3584
|
||||
msgid "Insufficient stock available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1809 templates/js/translated/order.js:3582
|
||||
#: templates/js/translated/build.js:1810 templates/js/translated/order.js:3582
|
||||
msgid "Sufficient stock available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1838 templates/js/translated/build.js:2083
|
||||
#: templates/js/translated/build.js:2656 templates/js/translated/order.js:3596
|
||||
#: templates/js/translated/build.js:1839 templates/js/translated/build.js:2084
|
||||
#: templates/js/translated/build.js:2657 templates/js/translated/order.js:3596
|
||||
msgid "Allocated"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1886 templates/js/translated/order.js:3676
|
||||
#: templates/js/translated/build.js:1887 templates/js/translated/order.js:3676
|
||||
msgid "Build stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1890 templates/stock_table.html:50
|
||||
#: templates/js/translated/build.js:1891 templates/stock_table.html:50
|
||||
msgid "Order stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1893 templates/js/translated/order.js:3669
|
||||
#: templates/js/translated/build.js:1894 templates/js/translated/order.js:3669
|
||||
msgid "Allocate stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1932 templates/js/translated/label.js:172
|
||||
#: templates/js/translated/build.js:1933 templates/js/translated/label.js:172
|
||||
#: templates/js/translated/order.js:756 templates/js/translated/order.js:2804
|
||||
#: templates/js/translated/report.js:225
|
||||
msgid "Select Parts"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1933 templates/js/translated/order.js:2805
|
||||
#: templates/js/translated/build.js:1934 templates/js/translated/order.js:2805
|
||||
msgid "You must select at least one part to allocate"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:1982 templates/js/translated/order.js:2753
|
||||
#: templates/js/translated/build.js:1983 templates/js/translated/order.js:2753
|
||||
msgid "Specify stock allocation quantity"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2056
|
||||
#: templates/js/translated/build.js:2057
|
||||
msgid "All Parts Allocated"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2057
|
||||
#: templates/js/translated/build.js:2058
|
||||
msgid "All selected parts have been fully allocated"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2071 templates/js/translated/order.js:2819
|
||||
#: templates/js/translated/build.js:2072 templates/js/translated/order.js:2819
|
||||
msgid "Select source location (leave blank to take from all locations)"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2099
|
||||
#: templates/js/translated/build.js:2100
|
||||
msgid "Allocate Stock Items to Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2110 templates/js/translated/order.js:2916
|
||||
#: templates/js/translated/build.js:2111 templates/js/translated/order.js:2916
|
||||
msgid "No matching stock locations"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2182 templates/js/translated/order.js:2993
|
||||
#: templates/js/translated/build.js:2183 templates/js/translated/order.js:2993
|
||||
msgid "No matching stock items"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2279
|
||||
#: templates/js/translated/build.js:2280
|
||||
msgid "Automatic Stock Allocation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2280
|
||||
#: templates/js/translated/build.js:2281
|
||||
msgid "Stock items will be automatically allocated to this build order, according to the provided guidelines"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2282
|
||||
#: templates/js/translated/build.js:2283
|
||||
msgid "If a location is specifed, stock will only be allocated from that location"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2283
|
||||
#: templates/js/translated/build.js:2284
|
||||
msgid "If stock is considered interchangeable, it will be allocated from the first location it is found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2284
|
||||
#: templates/js/translated/build.js:2285
|
||||
msgid "If substitute stock is allowed, it will be used where stock of the primary part cannot be found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2305
|
||||
#: templates/js/translated/build.js:2306
|
||||
msgid "Allocate Stock Items"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2412
|
||||
#: templates/js/translated/build.js:2413
|
||||
msgid "No builds matching query"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2447 templates/js/translated/part.js:1383
|
||||
#: templates/js/translated/build.js:2448 templates/js/translated/part.js:1383
|
||||
#: templates/js/translated/part.js:1850 templates/js/translated/stock.js:1682
|
||||
#: templates/js/translated/stock.js:2340
|
||||
msgid "Select"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2467
|
||||
#: templates/js/translated/build.js:2468
|
||||
msgid "Build order is overdue"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2495
|
||||
#: templates/js/translated/build.js:2496
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2531 templates/js/translated/stock.js:2582
|
||||
#: templates/js/translated/build.js:2532 templates/js/translated/stock.js:2582
|
||||
msgid "No user information"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/build.js:2637
|
||||
#: templates/js/translated/build.js:2638
|
||||
msgid "No parts allocated for"
|
||||
msgstr ""
|
||||
|
||||
@ -10058,61 +10070,61 @@ msgstr ""
|
||||
msgid "Select File Format"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:534
|
||||
#: templates/js/translated/tables.js:535
|
||||
msgid "Loading data"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:537
|
||||
#: templates/js/translated/tables.js:538
|
||||
msgid "rows per page"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:542
|
||||
#: templates/js/translated/tables.js:543
|
||||
msgid "Showing all rows"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:544
|
||||
#: templates/js/translated/tables.js:545
|
||||
msgid "Showing"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:544
|
||||
#: templates/js/translated/tables.js:545
|
||||
msgid "to"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:544
|
||||
#: templates/js/translated/tables.js:545
|
||||
msgid "of"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:544
|
||||
#: templates/js/translated/tables.js:545
|
||||
msgid "rows"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:548 templates/navbar.html:102
|
||||
#: templates/js/translated/tables.js:549 templates/navbar.html:102
|
||||
#: templates/search.html:8 templates/search_form.html:6
|
||||
#: templates/search_form.html:7
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:551
|
||||
#: templates/js/translated/tables.js:552
|
||||
msgid "No matching results"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:554
|
||||
#: templates/js/translated/tables.js:555
|
||||
msgid "Hide/Show pagination"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:557
|
||||
#: templates/js/translated/tables.js:558
|
||||
msgid "Refresh"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:560
|
||||
#: templates/js/translated/tables.js:561
|
||||
msgid "Toggle"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:563
|
||||
#: templates/js/translated/tables.js:564
|
||||
msgid "Columns"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/tables.js:566
|
||||
#: templates/js/translated/tables.js:567
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@
|
||||
- model: order.purchaseorder
|
||||
pk: 1
|
||||
fields:
|
||||
reference: '0001'
|
||||
reference: 'PO-0001'
|
||||
description: "Ordering some screws"
|
||||
supplier: 1
|
||||
status: 10 # Pending
|
||||
@ -13,7 +13,7 @@
|
||||
- model: order.purchaseorder
|
||||
pk: 2
|
||||
fields:
|
||||
reference: '0002'
|
||||
reference: 'PO-0002'
|
||||
description: "Ordering some more screws"
|
||||
supplier: 3
|
||||
status: 10 # Pending
|
||||
@ -21,7 +21,7 @@
|
||||
- model: order.purchaseorder
|
||||
pk: 3
|
||||
fields:
|
||||
reference: '0003'
|
||||
reference: 'PO-0003'
|
||||
description: 'Another PO'
|
||||
supplier: 3
|
||||
status: 20 # Placed
|
||||
@ -29,7 +29,7 @@
|
||||
- model: order.purchaseorder
|
||||
pk: 4
|
||||
fields:
|
||||
reference: '0004'
|
||||
reference: 'PO-0004'
|
||||
description: 'Another PO'
|
||||
supplier: 3
|
||||
status: 20 # Placed
|
||||
@ -37,7 +37,7 @@
|
||||
- model: order.purchaseorder
|
||||
pk: 5
|
||||
fields:
|
||||
reference: '0005'
|
||||
reference: 'PO-0005'
|
||||
description: 'Another PO'
|
||||
supplier: 3
|
||||
status: 30 # Complete
|
||||
@ -45,7 +45,7 @@
|
||||
- model: order.purchaseorder
|
||||
pk: 6
|
||||
fields:
|
||||
reference: '0006'
|
||||
reference: 'PO-0006'
|
||||
description: 'Another PO'
|
||||
supplier: 3
|
||||
status: 40 # Cancelled
|
||||
@ -54,7 +54,7 @@
|
||||
- model: order.purchaseorder
|
||||
pk: 7
|
||||
fields:
|
||||
reference: '0007'
|
||||
reference: 'PO-0007'
|
||||
description: 'Another PO'
|
||||
supplier: 2
|
||||
status: 10 # Pending
|
||||
|
@ -1,7 +1,6 @@
|
||||
# Generated by Django 3.2.4 on 2021-07-02 13:21
|
||||
|
||||
from django.db import migrations, models
|
||||
import order.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@ -14,11 +13,11 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='purchaseorder',
|
||||
name='reference',
|
||||
field=models.CharField(default=order.models.get_next_po_number, help_text='Order reference', max_length=64, unique=True, verbose_name='Reference'),
|
||||
field=models.CharField(default="PO", help_text='Order reference', max_length=64, unique=True, verbose_name='Reference'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='salesorder',
|
||||
name='reference',
|
||||
field=models.CharField(default=order.models.get_next_so_number, help_text='Order reference', max_length=64, unique=True, verbose_name='Reference'),
|
||||
field=models.CharField(default="SO", help_text='Order reference', max_length=64, unique=True, verbose_name='Reference'),
|
||||
),
|
||||
]
|
||||
|
@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.2.14 on 2022-07-07 11:55
|
||||
|
||||
from django.db import migrations, models
|
||||
import order.validators
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('order', '0071_auto_20220628_0133'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='salesorder',
|
||||
name='reference',
|
||||
field=models.CharField(default=order.validators.generate_next_sales_order_reference, help_text='Order reference', max_length=64, unique=True, validators=[order.validators.validate_sales_order_reference], verbose_name='Reference'),
|
||||
),
|
||||
]
|
@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.2.14 on 2022-07-09 01:01
|
||||
|
||||
from django.db import migrations, models
|
||||
import order.validators
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('order', '0072_alter_salesorder_reference'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='purchaseorder',
|
||||
name='reference',
|
||||
field=models.CharField(default=order.validators.generate_next_purchase_order_reference, help_text='Order reference', max_length=64, unique=True, validators=[order.validators.validate_purchase_order_reference], verbose_name='Reference'),
|
||||
),
|
||||
]
|
107
InvenTree/order/migrations/0074_auto_20220709_0108.py
Normal file
107
InvenTree/order/migrations/0074_auto_20220709_0108.py
Normal file
@ -0,0 +1,107 @@
|
||||
# Generated by Django 3.2.14 on 2022-07-09 01:08
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def update_order_references(order_model, prefix):
|
||||
"""Update all references of the given model, with the specified prefix"""
|
||||
|
||||
n = 0
|
||||
|
||||
for order in order_model.objects.all():
|
||||
if not order.reference.startswith(prefix):
|
||||
order.reference = prefix + order.reference
|
||||
order.save()
|
||||
|
||||
n += 1
|
||||
|
||||
return n
|
||||
|
||||
|
||||
def update_salesorder_reference(apps, schema_editor):
|
||||
"""Migrate the reference pattern for the SalesOrder model"""
|
||||
|
||||
# Extract the existing "prefix" value
|
||||
InvenTreeSetting = apps.get_model('common', 'inventreesetting')
|
||||
|
||||
try:
|
||||
prefix = InvenTreeSetting.objects.get(key='SALESORDER_REFERENCE_PREFIX').value
|
||||
except Exception:
|
||||
prefix = 'SO-'
|
||||
|
||||
# Construct a reference pattern
|
||||
pattern = prefix + '{ref:04d}'
|
||||
|
||||
# Create or update the BuildOrder.reference pattern
|
||||
try:
|
||||
setting = InvenTreeSetting.objects.get(key='SALESORDER_REFERENCE_PATTERN')
|
||||
setting.value = pattern
|
||||
setting.save()
|
||||
except InvenTreeSetting.DoesNotExist:
|
||||
setting = InvenTreeSetting.objects.create(
|
||||
key='SALESORDER_REFERENCE_PATTERN',
|
||||
value=pattern,
|
||||
)
|
||||
|
||||
# Update any existing sales order references
|
||||
SalesOrder = apps.get_model('order', 'salesorder')
|
||||
n = update_order_references(SalesOrder, prefix)
|
||||
|
||||
if n > 0:
|
||||
print(f"Updated reference field for {n} SalesOrder objects")
|
||||
|
||||
|
||||
def update_purchaseorder_reference(apps, schema_editor):
|
||||
"""Migrate the reference pattern for the PurchaseOrder model"""
|
||||
|
||||
# Extract the existing "prefix" value
|
||||
InvenTreeSetting = apps.get_model('common', 'inventreesetting')
|
||||
|
||||
try:
|
||||
prefix = InvenTreeSetting.objects.get(key='PURCHASEORDER_REFERENCE_PREFIX').value
|
||||
except Exception:
|
||||
prefix = 'PO-'
|
||||
|
||||
# Construct a reference pattern
|
||||
pattern = prefix + '{ref:04d}'
|
||||
|
||||
# Create or update the BuildOrder.reference pattern
|
||||
try:
|
||||
setting = InvenTreeSetting.objects.get(key='PURCHASEORDER_REFERENCE_PATTERN')
|
||||
setting.value = pattern
|
||||
setting.save()
|
||||
except InvenTreeSetting.DoesNotExist:
|
||||
setting = InvenTreeSetting.objects.create(
|
||||
key='PURCHASEORDER_REFERENCE_PATTERN',
|
||||
value=pattern,
|
||||
)
|
||||
|
||||
# Update any existing sales order references
|
||||
PurchaseOrder = apps.get_model('order', 'purchaseorder')
|
||||
n = update_order_references(PurchaseOrder, prefix)
|
||||
|
||||
if n > 0:
|
||||
print(f"Updated reference field for {n} PurchaseOrder objects")
|
||||
|
||||
|
||||
def nop(apps, schema_editor):
|
||||
"""Empty function for reverse migration"""
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('order', '0073_alter_purchaseorder_reference'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
update_salesorder_reference,
|
||||
reverse_code=nop,
|
||||
),
|
||||
migrations.RunPython(
|
||||
update_purchaseorder_reference,
|
||||
reverse_code=nop,
|
||||
)
|
||||
]
|
@ -24,14 +24,14 @@ from mptt.models import TreeForeignKey
|
||||
|
||||
import InvenTree.helpers
|
||||
import InvenTree.ready
|
||||
import order.validators
|
||||
from common.notifications import InvenTreeNotificationBodies
|
||||
from common.settings import currency_code_default
|
||||
from company.models import Company, SupplierPart
|
||||
from InvenTree.exceptions import log_error
|
||||
from InvenTree.fields import (InvenTreeModelMoneyField, InvenTreeNotesField,
|
||||
RoundingDecimalField)
|
||||
from InvenTree.helpers import (decimal2string, getSetting, increment,
|
||||
notify_responsible)
|
||||
from InvenTree.helpers import decimal2string, getSetting, notify_responsible
|
||||
from InvenTree.models import InvenTreeAttachment, ReferenceIndexingMixin
|
||||
from InvenTree.status_codes import (PurchaseOrderStatus, SalesOrderStatus,
|
||||
StockHistoryCode, StockStatus)
|
||||
@ -44,58 +44,6 @@ from users import models as UserModels
|
||||
logger = logging.getLogger('inventree')
|
||||
|
||||
|
||||
def get_next_po_number():
|
||||
"""Returns the next available PurchaseOrder reference number."""
|
||||
if PurchaseOrder.objects.count() == 0:
|
||||
return '0001'
|
||||
|
||||
order = PurchaseOrder.objects.exclude(reference=None).last()
|
||||
|
||||
attempts = {order.reference}
|
||||
|
||||
reference = order.reference
|
||||
|
||||
while 1:
|
||||
reference = increment(reference)
|
||||
|
||||
if reference in attempts:
|
||||
# Escape infinite recursion
|
||||
return reference
|
||||
|
||||
if PurchaseOrder.objects.filter(reference=reference).exists():
|
||||
attempts.add(reference)
|
||||
else:
|
||||
break
|
||||
|
||||
return reference
|
||||
|
||||
|
||||
def get_next_so_number():
|
||||
"""Returns the next available SalesOrder reference number."""
|
||||
if SalesOrder.objects.count() == 0:
|
||||
return '0001'
|
||||
|
||||
order = SalesOrder.objects.exclude(reference=None).last()
|
||||
|
||||
attempts = {order.reference}
|
||||
|
||||
reference = order.reference
|
||||
|
||||
while 1:
|
||||
reference = increment(reference)
|
||||
|
||||
if reference in attempts:
|
||||
# Escape infinite recursion
|
||||
return reference
|
||||
|
||||
if SalesOrder.objects.filter(reference=reference).exists():
|
||||
attempts.add(reference)
|
||||
else:
|
||||
break
|
||||
|
||||
return reference
|
||||
|
||||
|
||||
class Order(MetadataMixin, ReferenceIndexingMixin):
|
||||
"""Abstract model for an order.
|
||||
|
||||
@ -119,7 +67,7 @@ class Order(MetadataMixin, ReferenceIndexingMixin):
|
||||
|
||||
Ensures that the reference field is rebuilt whenever the instance is saved.
|
||||
"""
|
||||
self.rebuild_reference_field()
|
||||
self.reference_int = self.rebuild_reference_field(self.reference)
|
||||
|
||||
if not self.creation_date:
|
||||
self.creation_date = datetime.now().date()
|
||||
@ -230,8 +178,21 @@ class PurchaseOrder(Order):
|
||||
"""Return the API URL associated with the PurchaseOrder model"""
|
||||
return reverse('api-po-list')
|
||||
|
||||
@classmethod
|
||||
def api_defaults(cls, request):
|
||||
"""Return default values for thsi model when issuing an API OPTIONS request"""
|
||||
|
||||
defaults = {
|
||||
'reference': order.validators.generate_next_purchase_order_reference(),
|
||||
}
|
||||
|
||||
return defaults
|
||||
|
||||
OVERDUE_FILTER = Q(status__in=PurchaseOrderStatus.OPEN) & ~Q(target_date=None) & Q(target_date__lte=datetime.now().date())
|
||||
|
||||
# Global setting for specifying reference pattern
|
||||
REFERENCE_PATTERN_SETTING = 'PURCHASEORDER_REFERENCE_PATTERN'
|
||||
|
||||
@staticmethod
|
||||
def filterByDate(queryset, min_date, max_date):
|
||||
"""Filter by 'minimum and maximum date range'.
|
||||
@ -269,9 +230,8 @@ class PurchaseOrder(Order):
|
||||
|
||||
def __str__(self):
|
||||
"""Render a string representation of this PurchaseOrder"""
|
||||
prefix = getSetting('PURCHASEORDER_REFERENCE_PREFIX')
|
||||
|
||||
return f"{prefix}{self.reference} - {self.supplier.name if self.supplier else _('deleted')}"
|
||||
return f"{self.reference} - {self.supplier.name if self.supplier else _('deleted')}"
|
||||
|
||||
reference = models.CharField(
|
||||
unique=True,
|
||||
@ -279,7 +239,10 @@ class PurchaseOrder(Order):
|
||||
blank=False,
|
||||
verbose_name=_('Reference'),
|
||||
help_text=_('Order reference'),
|
||||
default=get_next_po_number,
|
||||
default=order.validators.generate_next_purchase_order_reference,
|
||||
validators=[
|
||||
order.validators.validate_purchase_order_reference,
|
||||
]
|
||||
)
|
||||
|
||||
status = models.PositiveIntegerField(default=PurchaseOrderStatus.PENDING, choices=PurchaseOrderStatus.items(),
|
||||
@ -595,8 +558,20 @@ class SalesOrder(Order):
|
||||
"""Return the API URL associated with the SalesOrder model"""
|
||||
return reverse('api-so-list')
|
||||
|
||||
@classmethod
|
||||
def api_defaults(cls, request):
|
||||
"""Return default values for this model when issuing an API OPTIONS request"""
|
||||
defaults = {
|
||||
'reference': order.validators.generate_next_sales_order_reference(),
|
||||
}
|
||||
|
||||
return defaults
|
||||
|
||||
OVERDUE_FILTER = Q(status__in=SalesOrderStatus.OPEN) & ~Q(target_date=None) & Q(target_date__lte=datetime.now().date())
|
||||
|
||||
# Global setting for specifying reference pattern
|
||||
REFERENCE_PATTERN_SETTING = 'SALESORDER_REFERENCE_PATTERN'
|
||||
|
||||
@staticmethod
|
||||
def filterByDate(queryset, min_date, max_date):
|
||||
"""Filter by "minimum and maximum date range".
|
||||
@ -634,9 +609,8 @@ class SalesOrder(Order):
|
||||
|
||||
def __str__(self):
|
||||
"""Render a string representation of this SalesOrder"""
|
||||
prefix = getSetting('SALESORDER_REFERENCE_PREFIX')
|
||||
|
||||
return f"{prefix}{self.reference} - {self.customer.name if self.customer else _('deleted')}"
|
||||
return f"{self.reference} - {self.customer.name if self.customer else _('deleted')}"
|
||||
|
||||
def get_absolute_url(self):
|
||||
"""Return the web URL for the detail view of this order"""
|
||||
@ -648,7 +622,10 @@ class SalesOrder(Order):
|
||||
blank=False,
|
||||
verbose_name=_('Reference'),
|
||||
help_text=_('Order reference'),
|
||||
default=get_next_so_number,
|
||||
default=order.validators.generate_next_sales_order_reference,
|
||||
validators=[
|
||||
order.validators.validate_sales_order_reference,
|
||||
]
|
||||
)
|
||||
|
||||
customer = models.ForeignKey(
|
||||
@ -957,6 +934,7 @@ class OrderExtraLine(OrderLineItem):
|
||||
max_digits=19,
|
||||
decimal_places=4,
|
||||
null=True, blank=True,
|
||||
allow_negative=True,
|
||||
verbose_name=_('Price'),
|
||||
help_text=_('Unit price'),
|
||||
)
|
||||
|
@ -23,8 +23,7 @@ from InvenTree.helpers import extract_serial_numbers, normalize
|
||||
from InvenTree.serializers import (InvenTreeAttachmentSerializer,
|
||||
InvenTreeDecimalField,
|
||||
InvenTreeModelSerializer,
|
||||
InvenTreeMoneySerializer,
|
||||
ReferenceIndexingSerializerMixin)
|
||||
InvenTreeMoneySerializer)
|
||||
from InvenTree.status_codes import (PurchaseOrderStatus, SalesOrderStatus,
|
||||
StockStatus)
|
||||
from part.serializers import PartBriefSerializer
|
||||
@ -86,7 +85,7 @@ class AbstractExtraLineMeta:
|
||||
]
|
||||
|
||||
|
||||
class PurchaseOrderSerializer(AbstractOrderSerializer, ReferenceIndexingSerializerMixin, InvenTreeModelSerializer):
|
||||
class PurchaseOrderSerializer(AbstractOrderSerializer, InvenTreeModelSerializer):
|
||||
"""Serializer for a PurchaseOrder object."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -130,6 +129,14 @@ class PurchaseOrderSerializer(AbstractOrderSerializer, ReferenceIndexingSerializ
|
||||
|
||||
reference = serializers.CharField(required=True)
|
||||
|
||||
def validate_reference(self, reference):
|
||||
"""Custom validation for the reference field"""
|
||||
|
||||
# Ensure that the reference matches the required pattern
|
||||
order.models.PurchaseOrder.validate_reference_field(reference)
|
||||
|
||||
return reference
|
||||
|
||||
responsible_detail = OwnerSerializer(source='responsible', read_only=True, many=False)
|
||||
|
||||
class Meta:
|
||||
@ -639,7 +646,7 @@ class PurchaseOrderAttachmentSerializer(InvenTreeAttachmentSerializer):
|
||||
]
|
||||
|
||||
|
||||
class SalesOrderSerializer(AbstractOrderSerializer, ReferenceIndexingSerializerMixin, InvenTreeModelSerializer):
|
||||
class SalesOrderSerializer(AbstractOrderSerializer, InvenTreeModelSerializer):
|
||||
"""Serializers for the SalesOrder object."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -683,6 +690,14 @@ class SalesOrderSerializer(AbstractOrderSerializer, ReferenceIndexingSerializerM
|
||||
|
||||
reference = serializers.CharField(required=True)
|
||||
|
||||
def validate_reference(self, reference):
|
||||
"""Custom validation for the reference field"""
|
||||
|
||||
# Ensure that the reference matches the required pattern
|
||||
order.models.SalesOrder.validate_reference_field(reference)
|
||||
|
||||
return reference
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options."""
|
||||
|
||||
|
@ -82,7 +82,7 @@ src="{% static 'img/blank_image.png' %}"
|
||||
<tr>
|
||||
<td><span class='fas fa-hashtag'></span></td>
|
||||
<td>{% trans "Order Reference" %}</td>
|
||||
<td>{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}{{ order.reference }}{% include "clip.html"%}</td>
|
||||
<td>{{ order.reference }}{% include "clip.html"%}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-info-circle'></span></td>
|
||||
@ -222,7 +222,7 @@ $("#edit-order").click(function() {
|
||||
constructForm('{% url "api-po-detail" order.pk %}', {
|
||||
fields: {
|
||||
reference: {
|
||||
prefix: global_settings.PURCHASEORDER_REFERENCE_PREFIX,
|
||||
icon: 'fa-hashtag',
|
||||
},
|
||||
{% if order.lines.count == 0 and order.status == PurchaseOrderStatus.PENDING %}
|
||||
supplier: {
|
||||
|
@ -78,7 +78,7 @@ src="{% static 'img/blank_image.png' %}"
|
||||
<tr>
|
||||
<td><span class='fas fa-hashtag'></span></td>
|
||||
<td>{% trans "Order Reference" %}</td>
|
||||
<td>{% settings_value 'SALESORDER_REFERENCE_PREFIX' %}{{ order.reference }}{% include "clip.html"%}</td>
|
||||
<td>{{ order.reference }}{% include "clip.html"%}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-info-circle'></span></td>
|
||||
@ -209,7 +209,7 @@ $("#edit-order").click(function() {
|
||||
constructForm('{% url "api-so-detail" order.pk %}', {
|
||||
fields: {
|
||||
reference: {
|
||||
prefix: global_settings.SALESORDER_REFERENCE_PREFIX,
|
||||
icon: 'fa-hashtag',
|
||||
},
|
||||
{% if order.lines.count == 0 and order.status == SalesOrderStatus.PENDING %}
|
||||
customer: {
|
||||
|
@ -94,23 +94,28 @@ class PurchaseOrderTest(OrderTest):
|
||||
self.assertEqual(data['description'], 'Ordering some screws')
|
||||
|
||||
def test_po_reference(self):
|
||||
"""Test that a reference with a too big / small reference is not possible."""
|
||||
"""Test that a reference with a too big / small reference is handled correctly."""
|
||||
# get permissions
|
||||
self.assignRole('purchase_order.add')
|
||||
|
||||
url = reverse('api-po-list')
|
||||
huge_number = 9223372036854775808
|
||||
huge_number = "PO-92233720368547758089999999999999999"
|
||||
|
||||
self.post(
|
||||
response = self.post(
|
||||
url,
|
||||
{
|
||||
'supplier': 1,
|
||||
'reference': huge_number,
|
||||
'description': 'PO not created via the API',
|
||||
'description': 'PO created via the API',
|
||||
},
|
||||
expected_code=201,
|
||||
)
|
||||
|
||||
order = models.PurchaseOrder.objects.get(pk=response.data['pk'])
|
||||
|
||||
self.assertEqual(order.reference, 'PO-92233720368547758089999999999999999')
|
||||
self.assertEqual(order.reference_int, 0x7fffffff)
|
||||
|
||||
def test_po_attachments(self):
|
||||
"""Test the list endpoint for the PurchaseOrderAttachment model"""
|
||||
url = reverse('api-po-attachment-list')
|
||||
@ -149,7 +154,7 @@ class PurchaseOrderTest(OrderTest):
|
||||
url,
|
||||
{
|
||||
'supplier': 1,
|
||||
'reference': '123456789-xyz',
|
||||
'reference': 'PO-123456789',
|
||||
'description': 'PO created via the API',
|
||||
},
|
||||
expected_code=201
|
||||
@ -177,19 +182,19 @@ class PurchaseOrderTest(OrderTest):
|
||||
# Get detail info!
|
||||
response = self.get(url)
|
||||
self.assertEqual(response.data['pk'], pk)
|
||||
self.assertEqual(response.data['reference'], '123456789-xyz')
|
||||
self.assertEqual(response.data['reference'], 'PO-123456789')
|
||||
|
||||
# Try to alter (edit) the PurchaseOrder
|
||||
response = self.patch(
|
||||
url,
|
||||
{
|
||||
'reference': '12345-abc',
|
||||
'reference': 'PO-12345',
|
||||
},
|
||||
expected_code=200
|
||||
)
|
||||
|
||||
# Reference should have changed
|
||||
self.assertEqual(response.data['reference'], '12345-abc')
|
||||
self.assertEqual(response.data['reference'], 'PO-12345')
|
||||
|
||||
# Now, let's try to delete it!
|
||||
# Initially, we do *not* have the required permission!
|
||||
@ -213,7 +218,7 @@ class PurchaseOrderTest(OrderTest):
|
||||
self.post(
|
||||
reverse('api-po-list'),
|
||||
{
|
||||
'reference': '12345678',
|
||||
'reference': 'PO-12345678',
|
||||
'supplier': 1,
|
||||
'description': 'A test purchase order',
|
||||
},
|
||||
@ -807,7 +812,7 @@ class SalesOrderTest(OrderTest):
|
||||
url,
|
||||
{
|
||||
'customer': 4,
|
||||
'reference': '12345',
|
||||
'reference': 'SO-12345',
|
||||
'description': 'Sales order',
|
||||
},
|
||||
expected_code=201
|
||||
@ -824,7 +829,7 @@ class SalesOrderTest(OrderTest):
|
||||
url,
|
||||
{
|
||||
'customer': 4,
|
||||
'reference': '12345',
|
||||
'reference': 'SO-12345',
|
||||
'description': 'Another sales order',
|
||||
},
|
||||
expected_code=400
|
||||
@ -834,19 +839,28 @@ class SalesOrderTest(OrderTest):
|
||||
|
||||
# Extract detail info for the SalesOrder
|
||||
response = self.get(url)
|
||||
self.assertEqual(response.data['reference'], '12345')
|
||||
self.assertEqual(response.data['reference'], 'SO-12345')
|
||||
|
||||
# Try to alter (edit) the SalesOrder
|
||||
# Initially try with an invalid reference field value
|
||||
response = self.patch(
|
||||
url,
|
||||
{
|
||||
'reference': '12345-a',
|
||||
'reference': 'SO-12345-a',
|
||||
},
|
||||
expected_code=400
|
||||
)
|
||||
|
||||
response = self.patch(
|
||||
url,
|
||||
{
|
||||
'reference': 'SO-12346',
|
||||
},
|
||||
expected_code=200
|
||||
)
|
||||
|
||||
# Reference should have changed
|
||||
self.assertEqual(response.data['reference'], '12345-a')
|
||||
self.assertEqual(response.data['reference'], 'SO-12346')
|
||||
|
||||
# Now, let's try to delete this SalesOrder
|
||||
# Initially, we do not have the required permission
|
||||
@ -866,14 +880,29 @@ class SalesOrderTest(OrderTest):
|
||||
"""Test that we can create a new SalesOrder via the API."""
|
||||
self.assignRole('sales_order.add')
|
||||
|
||||
self.post(
|
||||
reverse('api-so-list'),
|
||||
url = reverse('api-so-list')
|
||||
|
||||
# Will fail due to invalid reference field
|
||||
response = self.post(
|
||||
url,
|
||||
{
|
||||
'reference': '1234566778',
|
||||
'customer': 4,
|
||||
'description': 'A test sales order',
|
||||
},
|
||||
expected_code=201
|
||||
expected_code=400,
|
||||
)
|
||||
|
||||
self.assertIn('Reference must match required pattern', str(response.data['reference']))
|
||||
|
||||
self.post(
|
||||
url,
|
||||
{
|
||||
'reference': 'SO-12345',
|
||||
'customer': 4,
|
||||
'description': 'A better test sales order',
|
||||
},
|
||||
expected_code=201,
|
||||
)
|
||||
|
||||
def test_so_cancel(self):
|
||||
|
@ -40,19 +40,27 @@ class SalesOrderTest(TestCase):
|
||||
# Create a SalesOrder to ship against
|
||||
self.order = SalesOrder.objects.create(
|
||||
customer=self.customer,
|
||||
reference='1234',
|
||||
reference='SO-1234',
|
||||
customer_reference='ABC 55555'
|
||||
)
|
||||
|
||||
# Create a Shipment against this SalesOrder
|
||||
self.shipment = SalesOrderShipment.objects.create(
|
||||
order=self.order,
|
||||
reference='001',
|
||||
reference='SO-001',
|
||||
)
|
||||
|
||||
# Create a line item
|
||||
self.line = SalesOrderLineItem.objects.create(quantity=50, order=self.order, part=self.part)
|
||||
|
||||
def test_so_reference(self):
|
||||
"""Unit tests for sales order generation"""
|
||||
|
||||
# Test that a good reference is created when we have no existing orders
|
||||
SalesOrder.objects.all().delete()
|
||||
|
||||
self.assertEqual(SalesOrder.generate_reference(), 'SO-0001')
|
||||
|
||||
def test_rebuild_reference(self):
|
||||
"""Test that the 'reference_int' field gets rebuilt when the model is saved"""
|
||||
|
||||
|
@ -35,15 +35,17 @@ class OrderTest(TestCase):
|
||||
|
||||
def test_basics(self):
|
||||
"""Basic tests e.g. repr functions etc."""
|
||||
order = PurchaseOrder.objects.get(pk=1)
|
||||
|
||||
self.assertEqual(order.get_absolute_url(), '/order/purchase-order/1/')
|
||||
for pk in range(1, 8):
|
||||
|
||||
self.assertEqual(str(order), 'PO0001 - ACME')
|
||||
order = PurchaseOrder.objects.get(pk=pk)
|
||||
|
||||
self.assertEqual(order.get_absolute_url(), f'/order/purchase-order/{pk}/')
|
||||
|
||||
self.assertEqual(order.reference, f'PO-{pk:04d}')
|
||||
|
||||
line = PurchaseOrderLineItem.objects.get(pk=1)
|
||||
|
||||
self.assertEqual(str(line), "100 x ACME0001 from ACME (for PO0001 - ACME)")
|
||||
self.assertEqual(str(line), "100 x ACME0001 from ACME (for PO-0001 - ACME)")
|
||||
|
||||
def test_rebuild_reference(self):
|
||||
"""Test that the reference_int field is correctly updated when the model is saved"""
|
||||
|
49
InvenTree/order/validators.py
Normal file
49
InvenTree/order/validators.py
Normal file
@ -0,0 +1,49 @@
|
||||
"""Validation methods for the order app"""
|
||||
|
||||
|
||||
def generate_next_sales_order_reference():
|
||||
"""Generate the next available SalesOrder reference"""
|
||||
|
||||
from order.models import SalesOrder
|
||||
|
||||
return SalesOrder.generate_reference()
|
||||
|
||||
|
||||
def generate_next_purchase_order_reference():
|
||||
"""Generate the next available PurchasesOrder reference"""
|
||||
|
||||
from order.models import PurchaseOrder
|
||||
|
||||
return PurchaseOrder.generate_reference()
|
||||
|
||||
|
||||
def validate_sales_order_reference_pattern(pattern):
|
||||
"""Validate the SalesOrder reference 'pattern' setting"""
|
||||
|
||||
from order.models import SalesOrder
|
||||
|
||||
SalesOrder.validate_reference_pattern(pattern)
|
||||
|
||||
|
||||
def validate_purchase_order_reference_pattern(pattern):
|
||||
"""Validate the PurchaseOrder reference 'pattern' setting"""
|
||||
|
||||
from order.models import PurchaseOrder
|
||||
|
||||
PurchaseOrder.validate_reference_pattern(pattern)
|
||||
|
||||
|
||||
def validate_sales_order_reference(value):
|
||||
"""Validate that the SalesOrder reference field matches the required pattern"""
|
||||
|
||||
from order.models import SalesOrder
|
||||
|
||||
SalesOrder.validate_reference_field(value)
|
||||
|
||||
|
||||
def validate_purchase_order_reference(value):
|
||||
"""Validate that the PurchaseOrder reference field matches the required pattern"""
|
||||
|
||||
from order.models import PurchaseOrder
|
||||
|
||||
PurchaseOrder.validate_reference_field(value)
|
@ -1545,6 +1545,20 @@ class BomFilter(rest_filters.FilterSet):
|
||||
|
||||
return queryset
|
||||
|
||||
on_order = rest_filters.BooleanFilter(label="On order", method="filter_on_order")
|
||||
|
||||
def filter_on_order(self, queryset, name, value):
|
||||
"""Filter the queryset based on whether each line item has any stock on order"""
|
||||
|
||||
value = str2bool(value)
|
||||
|
||||
if value:
|
||||
queryset = queryset.filter(on_order__gt=0)
|
||||
else:
|
||||
queryset = queryset.filter(on_order=0)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
class BomList(ListCreateDestroyAPIView):
|
||||
"""API endpoint for accessing a list of BomItem objects.
|
||||
|
@ -524,6 +524,8 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
||||
|
||||
purchase_price_range = serializers.SerializerMethodField()
|
||||
|
||||
on_order = serializers.FloatField(read_only=True)
|
||||
|
||||
# Annotated fields for available stock
|
||||
available_stock = serializers.FloatField(read_only=True)
|
||||
available_substitute_stock = serializers.FloatField(read_only=True)
|
||||
@ -593,6 +595,11 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
||||
|
||||
ref = 'sub_part__'
|
||||
|
||||
# Annotate with the total "on order" amount for the sub-part
|
||||
queryset = queryset.annotate(
|
||||
on_order=part.filters.annotate_on_order_quantity(ref),
|
||||
)
|
||||
|
||||
# Calculate "total stock" for the referenced sub_part
|
||||
# Calculate the "build_order_allocations" for the sub_part
|
||||
# Note that these fields are only aliased, not annotated
|
||||
@ -719,6 +726,8 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
||||
'available_substitute_stock',
|
||||
'available_variant_stock',
|
||||
|
||||
# Annotated field describing quantity on order
|
||||
'on_order',
|
||||
]
|
||||
|
||||
|
||||
|
@ -1514,6 +1514,7 @@ class PartAPIAggregationTest(InvenTreeAPITestCase):
|
||||
part=Part.objects.get(pk=101),
|
||||
quantity=10,
|
||||
title='Making some assemblies',
|
||||
reference='BO-9999',
|
||||
status=BuildStatus.PRODUCTION,
|
||||
)
|
||||
|
||||
|
@ -6,6 +6,7 @@ import os
|
||||
import pathlib
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
from importlib.metadata import metadata
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.utils import OperationalError, ProgrammingError
|
||||
@ -195,11 +196,26 @@ class InvenTreePlugin(MixinBase, MetaBase):
|
||||
|
||||
self.define_package()
|
||||
|
||||
def _get_value(self, meta_name: str, package_name: str) -> str:
|
||||
"""Extract values from class meta or package info.
|
||||
|
||||
Args:
|
||||
meta_name (str): Name of the class meta to use.
|
||||
package_name (str): Name of the package data to use.
|
||||
|
||||
Returns:
|
||||
str: Extracted value, None if nothing found.
|
||||
"""
|
||||
val = getattr(self, meta_name, None)
|
||||
if not val:
|
||||
val = self.package.get(package_name, None)
|
||||
return val
|
||||
|
||||
# region properties
|
||||
@property
|
||||
def description(self):
|
||||
"""Description of plugin."""
|
||||
description = getattr(self, 'DESCRIPTION', None)
|
||||
description = self._get_value('DESCRIPTION', 'description')
|
||||
if not description:
|
||||
description = self.plugin_name()
|
||||
return description
|
||||
@ -207,9 +223,7 @@ class InvenTreePlugin(MixinBase, MetaBase):
|
||||
@property
|
||||
def author(self):
|
||||
"""Author of plugin - either from plugin settings or git."""
|
||||
author = getattr(self, 'AUTHOR', None)
|
||||
if not author:
|
||||
author = self.package.get('author')
|
||||
author = self._get_value('AUTHOR', 'author')
|
||||
if not author:
|
||||
author = _('No author found') # pragma: no cover
|
||||
return author
|
||||
@ -229,19 +243,19 @@ class InvenTreePlugin(MixinBase, MetaBase):
|
||||
@property
|
||||
def version(self):
|
||||
"""Version of plugin."""
|
||||
version = getattr(self, 'VERSION', None)
|
||||
version = self._get_value('VERSION', 'version')
|
||||
return version
|
||||
|
||||
@property
|
||||
def website(self):
|
||||
"""Website of plugin - if set else None."""
|
||||
website = getattr(self, 'WEBSITE', None)
|
||||
website = self._get_value('WEBSITE', 'website')
|
||||
return website
|
||||
|
||||
@property
|
||||
def license(self):
|
||||
"""License of plugin."""
|
||||
lic = getattr(self, 'LICENSE', None)
|
||||
lic = self._get_value('LICENSE', 'license')
|
||||
return lic
|
||||
# endregion
|
||||
|
||||
@ -273,9 +287,18 @@ class InvenTreePlugin(MixinBase, MetaBase):
|
||||
"""Get last git commit for the plugin."""
|
||||
return get_git_log(self.def_path)
|
||||
|
||||
def _get_package_metadata(self):
|
||||
@classmethod
|
||||
def _get_package_metadata(cls):
|
||||
"""Get package metadata for plugin."""
|
||||
return {} # pragma: no cover # TODO add usage for package metadata
|
||||
meta = metadata(cls.__name__)
|
||||
|
||||
return {
|
||||
'author': meta['Author-email'],
|
||||
'description': meta['Summary'],
|
||||
'version': meta['Version'],
|
||||
'website': meta['Project-URL'],
|
||||
'license': meta['License']
|
||||
}
|
||||
|
||||
def define_package(self):
|
||||
"""Add package info of the plugin into plugins context."""
|
||||
|
@ -208,6 +208,7 @@ class PluginsRegistry:
|
||||
try:
|
||||
plugin = entry.load()
|
||||
plugin.is_package = True
|
||||
plugin._get_package_metadata()
|
||||
self.plugin_modules.append(plugin)
|
||||
except Exception as error:
|
||||
handle_error(error, do_raise=False, log_name='discovery')
|
||||
|
@ -425,7 +425,6 @@ class PurchaseOrderReport(ReportTemplateBase):
|
||||
'order': order,
|
||||
'reference': order.reference,
|
||||
'supplier': order.supplier,
|
||||
'prefix': common.models.InvenTreeSetting.get_setting('PURCHASEORDER_REFERENCE_PREFIX'),
|
||||
'title': str(order),
|
||||
}
|
||||
|
||||
@ -463,7 +462,6 @@ class SalesOrderReport(ReportTemplateBase):
|
||||
'lines': order.lines,
|
||||
'extra_lines': order.extra_lines,
|
||||
'order': order,
|
||||
'prefix': common.models.InvenTreeSetting.get_setting('SALESORDER_REFERENCE_PREFIX'),
|
||||
'reference': order.reference,
|
||||
'title': str(order),
|
||||
}
|
||||
|
@ -30,8 +30,7 @@ import report.models
|
||||
from company import models as CompanyModels
|
||||
from InvenTree.fields import (InvenTreeModelMoneyField, InvenTreeNotesField,
|
||||
InvenTreeURLField)
|
||||
from InvenTree.models import InvenTreeAttachment, InvenTreeTree
|
||||
from InvenTree.serializers import extract_int
|
||||
from InvenTree.models import InvenTreeAttachment, InvenTreeTree, extract_int
|
||||
from InvenTree.status_codes import StockHistoryCode, StockStatus
|
||||
from part import models as PartModels
|
||||
from plugin.events import trigger_event
|
||||
@ -1708,8 +1707,7 @@ class StockItem(MetadataMixin, MPTTModel):
|
||||
s += ' @ {loc}'.format(loc=self.location.name)
|
||||
|
||||
if self.purchase_order:
|
||||
s += " ({pre}{po})".format(
|
||||
pre=InvenTree.helpers.getSetting("PURCHASEORDER_REFERENCE_PREFIX"),
|
||||
s += " ({po})".format(
|
||||
po=self.purchase_order,
|
||||
)
|
||||
|
||||
|
@ -20,7 +20,8 @@ import InvenTree.serializers
|
||||
import part.models as part_models
|
||||
from common.settings import currency_code_default, currency_code_mappings
|
||||
from company.serializers import SupplierPartSerializer
|
||||
from InvenTree.serializers import InvenTreeDecimalField, extract_int
|
||||
from InvenTree.models import extract_int
|
||||
from InvenTree.serializers import InvenTreeDecimalField
|
||||
from part.serializers import PartBriefSerializer
|
||||
|
||||
from .models import (StockItem, StockItemAttachment, StockItemTestResult,
|
||||
@ -67,8 +68,8 @@ class StockItemSerializerBrief(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
|
||||
def validate_serial(self, value):
|
||||
"""Make sure serial is not to big."""
|
||||
if extract_int(value) > 2147483647:
|
||||
raise serializers.ValidationError('serial is to to big')
|
||||
if abs(extract_int(value)) > 0x7fffffff:
|
||||
raise serializers.ValidationError(_("Serial number is too large"))
|
||||
return value
|
||||
|
||||
|
||||
|
@ -297,6 +297,7 @@
|
||||
stock_item: {{ item.pk }},
|
||||
}
|
||||
},
|
||||
multi_delete: true,
|
||||
method: 'DELETE',
|
||||
title: '{% trans "Delete Test Data" %}',
|
||||
preFormContent: html,
|
||||
|
@ -87,7 +87,7 @@ class StockTest(InvenTreeTestCase):
|
||||
# And there should be *no* items being build
|
||||
self.assertEqual(part.quantity_being_built, 0)
|
||||
|
||||
build = Build.objects.create(reference='12345', part=part, title='A test build', quantity=1)
|
||||
build = Build.objects.create(reference='BO-4444', part=part, title='A test build', quantity=1)
|
||||
|
||||
# Add some stock items which are "building"
|
||||
for _ in range(10):
|
||||
@ -395,13 +395,14 @@ class StockTest(InvenTreeTestCase):
|
||||
item.serial = "-123"
|
||||
item.save()
|
||||
|
||||
# Negative number should map to zero
|
||||
self.assertEqual(item.serial_int, 0)
|
||||
# Negative number should map to positive value
|
||||
self.assertEqual(item.serial_int, 123)
|
||||
|
||||
# Test a very very large value
|
||||
item.serial = '99999999999999999999999999999999999999999999999999999'
|
||||
item.save()
|
||||
|
||||
# The 'integer' portion has been clipped to a maximum value
|
||||
self.assertEqual(item.serial_int, 0x7fffffff)
|
||||
|
||||
# Non-numeric values should encode to zero
|
||||
|
@ -72,6 +72,7 @@ $('#history-delete').click(function() {
|
||||
'{% url "api-notifications-list" %}',
|
||||
{
|
||||
method: 'DELETE',
|
||||
multi_delete: true,
|
||||
preFormContent: html,
|
||||
title: '{% trans "Delete Notifications" %}',
|
||||
onSuccess: function() {
|
||||
|
@ -12,8 +12,7 @@
|
||||
|
||||
<table class='table table-striped table-condensed'>
|
||||
<tbody>
|
||||
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_PREFIX" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_REGEX" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_PATTERN" %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -18,7 +18,9 @@
|
||||
{% include "InvenTree/settings/setting.html" with key="INVENTREE_RESTRICT_ABOUT" icon="fa-info-circle" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="INVENTREE_BASE_URL" icon="fa-globe" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="INVENTREE_COMPANY_NAME" icon="fa-building" %}
|
||||
<tr><td colspan='5'></td></tr>
|
||||
{% include "InvenTree/settings/setting.html" with key="INVENTREE_DOWNLOAD_FROM_URL" icon="fa-cloud-download-alt" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="INVENTREE_REQUIRE_CONFIRM" icon="fa-check" %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
{% block content %}
|
||||
<table class='table table-striped table-condensed'>
|
||||
<tbody>
|
||||
{% include "InvenTree/settings/setting.html" with key="PURCHASEORDER_REFERENCE_PREFIX" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="PURCHASEORDER_REFERENCE_PATTERN" %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
<table class='table table-striped table-condensed'>
|
||||
<tbody>
|
||||
{% include "InvenTree/settings/setting.html" with key="SALESORDER_REFERENCE_PREFIX" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="SALESORDER_REFERENCE_PATTERN" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="SALESORDER_DEFAULT_SHIPMENT" icon="fa-truck-loading" %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -81,7 +81,7 @@
|
||||
<td>
|
||||
<span style="display: none;" id="about-copy-text">{% include "version.html" %}</span>
|
||||
<span class="float-right">
|
||||
<button class="btn clip-btn-version" type="button" data-bs-toggle='tooltip' title='{% trans "copy to clipboard" %}'><em class="fas fa-copy"></em> {% trans "copy version information" %}</button>
|
||||
<button class="btn clip-btn" type="button" data-bs-toggle='tooltip' title='{% trans "copy to clipboard" %}'><em class="fas fa-copy"></em> {% trans "copy version information" %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -109,6 +109,7 @@ function deleteAttachments(attachments, url, options={}) {
|
||||
|
||||
constructForm(url, {
|
||||
method: 'DELETE',
|
||||
multi_delete: true,
|
||||
title: '{% trans "Delete Attachments" %}',
|
||||
preFormContent: html,
|
||||
form_data: {
|
||||
|
@ -292,8 +292,8 @@ function exportBom(part_id, options={}) {
|
||||
choices: exportFormatOptions(),
|
||||
},
|
||||
cascade: {
|
||||
label: '{% trans "Cascading" %}',
|
||||
help_text: '{% trans "Download cascading / multi-level BOM" %}',
|
||||
label: '{% trans "Multi Level BOM" %}',
|
||||
help_text: '{% trans "Include BOM data for subassemblies" %}',
|
||||
type: 'boolean',
|
||||
value: inventreeLoad('bom-export-cascading', true),
|
||||
},
|
||||
@ -302,6 +302,7 @@ function exportBom(part_id, options={}) {
|
||||
help_text: '{% trans "Select maximum number of BOM levels to export (0 = all levels)" %}',
|
||||
type: 'integer',
|
||||
value: 0,
|
||||
required: true,
|
||||
min_value: 0,
|
||||
},
|
||||
parameter_data: {
|
||||
@ -651,9 +652,10 @@ function bomSubstitutesDialog(bom_item_id, substitutes, options={}) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Delete the selected BOM items from the database
|
||||
*/
|
||||
function deleteBomItems(items, options={}) {
|
||||
/* Delete the selected BOM items from the database
|
||||
*/
|
||||
|
||||
function renderItem(item, opts={}) {
|
||||
|
||||
@ -696,6 +698,7 @@ function deleteBomItems(items, options={}) {
|
||||
|
||||
constructForm('{% url "api-bom-list" %}', {
|
||||
method: 'DELETE',
|
||||
multi_delete: true,
|
||||
title: '{% trans "Delete selected BOM items?" %}',
|
||||
form_data: {
|
||||
items: ids,
|
||||
@ -877,6 +880,32 @@ function loadBomTable(table, options={}) {
|
||||
|
||||
return text;
|
||||
},
|
||||
footerFormatter: function(data) {
|
||||
|
||||
// Top-level BOM count
|
||||
var top_total = 0;
|
||||
|
||||
// Total BOM count
|
||||
var all_total = 0;
|
||||
|
||||
data.forEach(function(row) {
|
||||
var q = +row['quantity'] || 0;
|
||||
|
||||
all_total += q;
|
||||
|
||||
if (row.part == options.parent_id) {
|
||||
top_total += q;
|
||||
}
|
||||
});
|
||||
|
||||
var total = `${top_total}`;
|
||||
|
||||
if (top_total != all_total) {
|
||||
total += ` / ${all_total}`;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
});
|
||||
|
||||
cols.push({
|
||||
@ -897,9 +926,10 @@ function loadBomTable(table, options={}) {
|
||||
var text = `${available_stock}`;
|
||||
|
||||
if (available_stock <= 0) {
|
||||
text = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock Available" %}</span>`;
|
||||
text += `<span class='fas fa-times-circle icon-red float-right' title='{% trans "No Stock Available" %}'></span>`;
|
||||
} else {
|
||||
var extra = '';
|
||||
|
||||
if ((substitute_stock > 0) && (variant_stock > 0)) {
|
||||
extra = '{% trans "Includes variant and substitute stock" %}';
|
||||
} else if (variant_stock > 0) {
|
||||
@ -913,6 +943,10 @@ function loadBomTable(table, options={}) {
|
||||
}
|
||||
}
|
||||
|
||||
if (row.on_order && row.on_order > 0) {
|
||||
text += `<span class='fas fa-shopping-cart float-right' title='{% trans "On Order" %}: ${row.on_order}'></span>`;
|
||||
}
|
||||
|
||||
return renderLink(text, url);
|
||||
}
|
||||
});
|
||||
@ -1010,7 +1044,36 @@ function loadBomTable(table, options={}) {
|
||||
can_build = available / row.quantity;
|
||||
}
|
||||
|
||||
return formatDecimal(can_build, 2);
|
||||
var text = formatDecimal(can_build, 2);
|
||||
|
||||
// Take "on order" quantity into account
|
||||
if (row.on_order && row.on_order > 0 && row.quantity > 0) {
|
||||
available += row.on_order;
|
||||
can_build = available / row.quantity;
|
||||
|
||||
text += `<span class='fas fa-info-circle icon-blue float-right' title='{% trans "Including On Order" %}: ${can_build}'></span>`;
|
||||
}
|
||||
|
||||
return text;
|
||||
},
|
||||
footerFormatter: function(data) {
|
||||
var can_build = null;
|
||||
|
||||
data.forEach(function(row) {
|
||||
if (row.part == options.parent_id && row.quantity > 0) {
|
||||
var cb = availableQuantity(row) / row.quantity;
|
||||
|
||||
if (can_build == null || cb < can_build) {
|
||||
can_build = cb;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (can_build == null) {
|
||||
can_build = '-';
|
||||
}
|
||||
|
||||
return can_build;
|
||||
},
|
||||
sorter: function(valA, valB, rowA, rowB) {
|
||||
// Function to sort the "can build" quantity
|
||||
@ -1131,6 +1194,7 @@ function loadBomTable(table, options={}) {
|
||||
parentIdField: 'parentId',
|
||||
treeShowField: 'sub_part',
|
||||
showColumns: true,
|
||||
showFooter: true,
|
||||
name: 'bom',
|
||||
sortable: true,
|
||||
search: true,
|
||||
|
@ -4,7 +4,6 @@
|
||||
/* globals
|
||||
buildStatusDisplay,
|
||||
constructForm,
|
||||
global_settings,
|
||||
imageHoverIcon,
|
||||
inventreeGet,
|
||||
launchModalForm,
|
||||
@ -36,7 +35,7 @@
|
||||
function buildFormFields() {
|
||||
return {
|
||||
reference: {
|
||||
prefix: global_settings.BUILDORDER_REFERENCE_PREFIX,
|
||||
icon: 'fa-hashtag',
|
||||
},
|
||||
part: {
|
||||
filters: {
|
||||
@ -731,9 +730,8 @@ function loadBuildOrderAllocationTable(table, options={}) {
|
||||
switchable: false,
|
||||
title: '{% trans "Build Order" %}',
|
||||
formatter: function(value, row) {
|
||||
var prefix = global_settings.BUILDORDER_REFERENCE_PREFIX;
|
||||
|
||||
var ref = `${prefix}${row.build_detail.reference}`;
|
||||
var ref = `${row.build_detail.reference}`;
|
||||
|
||||
return renderLink(ref, `/build/${row.build}/`);
|
||||
}
|
||||
@ -2372,7 +2370,6 @@ function loadBuildTable(table, options) {
|
||||
filters,
|
||||
{
|
||||
success: function(response) {
|
||||
var prefix = global_settings.BUILDORDER_REFERENCE_PREFIX;
|
||||
|
||||
for (var idx = 0; idx < response.length; idx++) {
|
||||
|
||||
@ -2386,7 +2383,7 @@ function loadBuildTable(table, options) {
|
||||
date = order.target_date;
|
||||
}
|
||||
|
||||
var title = `${prefix}${order.reference}`;
|
||||
var title = `${order.reference}`;
|
||||
|
||||
var color = '#4c68f5';
|
||||
|
||||
@ -2460,12 +2457,6 @@ function loadBuildTable(table, options) {
|
||||
switchable: true,
|
||||
formatter: function(value, row) {
|
||||
|
||||
var prefix = global_settings.BUILDORDER_REFERENCE_PREFIX;
|
||||
|
||||
if (prefix) {
|
||||
value = `${prefix}${value}`;
|
||||
}
|
||||
|
||||
var html = renderLink(value, '/build/' + row.pk + '/');
|
||||
|
||||
if (row.overdue) {
|
||||
|
@ -263,6 +263,7 @@ function deleteSupplierParts(parts, options={}) {
|
||||
|
||||
constructForm('{% url "api-supplier-part-list" %}', {
|
||||
method: 'DELETE',
|
||||
multi_delete: true,
|
||||
title: '{% trans "Delete Supplier Parts" %}',
|
||||
preFormContent: html,
|
||||
form_data: {
|
||||
@ -491,6 +492,7 @@ function deleteManufacturerParts(selections, options={}) {
|
||||
|
||||
constructForm('{% url "api-manufacturer-part-list" %}', {
|
||||
method: 'DELETE',
|
||||
multi_delete: true,
|
||||
title: '{% trans "Delete Manufacturer Parts" %}',
|
||||
preFormContent: html,
|
||||
form_data: {
|
||||
@ -538,6 +540,7 @@ function deleteManufacturerPartParameters(selections, options={}) {
|
||||
|
||||
constructForm('{% url "api-manufacturer-part-parameter-list" %}', {
|
||||
method: 'DELETE',
|
||||
multi_delete: true,
|
||||
title: '{% trans "Delete Parameters" %}',
|
||||
preFormContent: html,
|
||||
form_data: {
|
||||
|
@ -6,6 +6,7 @@
|
||||
inventreeFormDataUpload,
|
||||
inventreeGet,
|
||||
inventreePut,
|
||||
global_settings,
|
||||
modalEnable,
|
||||
modalShowSubmitButton,
|
||||
renderBuild,
|
||||
@ -250,30 +251,40 @@ function constructChangeForm(fields, options) {
|
||||
*/
|
||||
function constructDeleteForm(fields, options) {
|
||||
|
||||
// Request existing data from the API endpoint
|
||||
// This data can be used to render some information on the form
|
||||
$.ajax({
|
||||
url: options.url,
|
||||
type: 'GET',
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
accepts: {
|
||||
json: 'application/json',
|
||||
},
|
||||
success: function(data) {
|
||||
// If we are deleting a specific "instance" (i.e. a single object)
|
||||
// then we request the instance information first
|
||||
|
||||
// Store the instance data
|
||||
options.instance = data;
|
||||
// However we may be performing a "multi-delete" (against a list endpoint),
|
||||
// in which case we do not want to perform such a request!
|
||||
|
||||
constructFormBody(fields, options);
|
||||
},
|
||||
error: function(xhr) {
|
||||
// TODO: Handle error here
|
||||
console.error(`Error in constructDeleteForm at '${options.url}`);
|
||||
if (options.multi_delete) {
|
||||
constructFormBody(fields, options);
|
||||
} else {
|
||||
// Request existing data from the API endpoint
|
||||
// This data can be used to render some information on the form
|
||||
$.ajax({
|
||||
url: options.url,
|
||||
type: 'GET',
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
accepts: {
|
||||
json: 'application/json',
|
||||
},
|
||||
success: function(data) {
|
||||
|
||||
showApiError(xhr, options.url);
|
||||
}
|
||||
});
|
||||
// Store the instance data
|
||||
options.instance = data;
|
||||
|
||||
constructFormBody(fields, options);
|
||||
},
|
||||
error: function(xhr) {
|
||||
// TODO: Handle error here
|
||||
console.error(`Error in constructDeleteForm at '${options.url}`);
|
||||
|
||||
showApiError(xhr, options.url);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -565,7 +576,7 @@ function constructFormBody(fields, options) {
|
||||
$(modal).find('#modal-footer-buttons').html('');
|
||||
|
||||
// Insert "confirm" button (if required)
|
||||
if (options.confirm) {
|
||||
if (options.confirm && global_settings.INVENTREE_REQUIRE_CONFIRM) {
|
||||
insertConfirmButton(options);
|
||||
}
|
||||
|
||||
|
@ -255,8 +255,7 @@ function renderOwner(name, data, parameters={}, options={}) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderPurchaseOrder(name, data, parameters={}, options={}) {
|
||||
|
||||
var prefix = global_settings.PURCHASEORDER_REFERENCE_PREFIX;
|
||||
var html = `<span>${prefix}${data.reference}</span>`;
|
||||
var html = `<span>${data.reference}</span>`;
|
||||
|
||||
var thumbnail = null;
|
||||
|
||||
@ -281,8 +280,7 @@ function renderPurchaseOrder(name, data, parameters={}, options={}) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderSalesOrder(name, data, parameters={}, options={}) {
|
||||
|
||||
var prefix = global_settings.SALESORDER_REFERENCE_PREFIX;
|
||||
var html = `<span>${prefix}${data.reference}</span>`;
|
||||
var html = `<span>${data.reference}</span>`;
|
||||
|
||||
var thumbnail = null;
|
||||
|
||||
@ -307,10 +305,8 @@ function renderSalesOrder(name, data, parameters={}, options={}) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderSalesOrderShipment(name, data, parameters={}, options={}) {
|
||||
|
||||
var so_prefix = global_settings.SALESORDER_REFERENCE_PREFIX;
|
||||
|
||||
var html = `
|
||||
<span>${so_prefix}${data.order_detail.reference} - {% trans "Shipment" %} ${data.reference}</span>
|
||||
<span>${data.order_detail.reference} - {% trans "Shipment" %} ${data.reference}</span>
|
||||
<span class='float-right'>
|
||||
<small>{% trans "Shipment ID" %}: ${data.pk}</small>
|
||||
</span>
|
||||
|
@ -431,7 +431,7 @@ function createSalesOrderShipment(options={}) {
|
||||
var fields = salesOrderShipmentFields(options);
|
||||
|
||||
fields.reference.value = ref;
|
||||
fields.reference.prefix = global_settings.SALESORDER_REFERENCE_PREFIX + options.reference;
|
||||
fields.reference.prefix = options.reference;
|
||||
|
||||
constructForm('{% url "api-so-shipment-list" %}', {
|
||||
method: 'POST',
|
||||
@ -456,7 +456,7 @@ function createSalesOrder(options={}) {
|
||||
method: 'POST',
|
||||
fields: {
|
||||
reference: {
|
||||
prefix: global_settings.SALESORDER_REFERENCE_PREFIX,
|
||||
icon: 'fa-hashtag',
|
||||
},
|
||||
customer: {
|
||||
value: options.customer,
|
||||
@ -497,7 +497,7 @@ function createPurchaseOrder(options={}) {
|
||||
method: 'POST',
|
||||
fields: {
|
||||
reference: {
|
||||
prefix: global_settings.PURCHASEORDER_REFERENCE_PREFIX,
|
||||
icon: 'fa-hashtag',
|
||||
},
|
||||
supplier: {
|
||||
icon: 'fa-building',
|
||||
@ -1081,9 +1081,7 @@ function newPurchaseOrderFromOrderWizard(e) {
|
||||
},
|
||||
{
|
||||
success: function(response) {
|
||||
var text = global_settings.PURCHASEORDER_REFERENCE_PREFIX || '';
|
||||
|
||||
text += response.reference;
|
||||
var text = response.reference;
|
||||
|
||||
if (response.supplier_detail) {
|
||||
text += ` ${response.supplier_detail.name}`;
|
||||
@ -1545,8 +1543,6 @@ function loadPurchaseOrderTable(table, options) {
|
||||
filters,
|
||||
{
|
||||
success: function(response) {
|
||||
var prefix = global_settings.PURCHASEORDER_REFERENCE_PREFIX;
|
||||
|
||||
for (var idx = 0; idx < response.length; idx++) {
|
||||
|
||||
var order = response[idx];
|
||||
@ -1559,7 +1555,7 @@ function loadPurchaseOrderTable(table, options) {
|
||||
date = order.target_date;
|
||||
}
|
||||
|
||||
var title = `${prefix}${order.reference} - ${order.supplier_detail.name}`;
|
||||
var title = `${order.reference} - ${order.supplier_detail.name}`;
|
||||
|
||||
var color = '#4c68f5';
|
||||
|
||||
@ -1623,12 +1619,6 @@ function loadPurchaseOrderTable(table, options) {
|
||||
switchable: false,
|
||||
formatter: function(value, row) {
|
||||
|
||||
var prefix = global_settings.PURCHASEORDER_REFERENCE_PREFIX;
|
||||
|
||||
if (prefix) {
|
||||
value = `${prefix}${value}`;
|
||||
}
|
||||
|
||||
var html = renderLink(value, `/order/purchase-order/${row.pk}/`);
|
||||
|
||||
if (row.overdue) {
|
||||
@ -2336,8 +2326,6 @@ function loadSalesOrderTable(table, options) {
|
||||
{
|
||||
success: function(response) {
|
||||
|
||||
var prefix = global_settings.SALESORDER_REFERENCE_PREFIX;
|
||||
|
||||
for (var idx = 0; idx < response.length; idx++) {
|
||||
var order = response[idx];
|
||||
|
||||
@ -2349,7 +2337,7 @@ function loadSalesOrderTable(table, options) {
|
||||
date = order.target_date;
|
||||
}
|
||||
|
||||
var title = `${prefix}${order.reference} - ${order.customer_detail.name}`;
|
||||
var title = `${order.reference} - ${order.customer_detail.name}`;
|
||||
|
||||
// Default color is blue
|
||||
var color = '#4c68f5';
|
||||
@ -2435,13 +2423,6 @@ function loadSalesOrderTable(table, options) {
|
||||
field: 'reference',
|
||||
title: '{% trans "Sales Order" %}',
|
||||
formatter: function(value, row) {
|
||||
|
||||
var prefix = global_settings.SALESORDER_REFERENCE_PREFIX;
|
||||
|
||||
if (prefix) {
|
||||
value = `${prefix}${value}`;
|
||||
}
|
||||
|
||||
var html = renderLink(value, `/order/sales-order/${row.pk}/`);
|
||||
|
||||
if (row.overdue) {
|
||||
@ -2891,7 +2872,7 @@ function allocateStockToSalesOrder(order_id, line_items, options={}) {
|
||||
var fields = salesOrderShipmentFields(options);
|
||||
|
||||
fields.reference.value = ref;
|
||||
fields.reference.prefix = global_settings.SALESORDER_REFERENCE_PREFIX + options.reference;
|
||||
fields.reference.prefix = options.reference;
|
||||
|
||||
return fields;
|
||||
}
|
||||
@ -3123,9 +3104,7 @@ function loadSalesOrderAllocationTable(table, options={}) {
|
||||
title: '{% trans "Order" %}',
|
||||
formatter: function(value, row) {
|
||||
|
||||
var prefix = global_settings.SALESORDER_REFERENCE_PREFIX;
|
||||
|
||||
var ref = `${prefix}${row.order_detail.reference}`;
|
||||
var ref = `${row.order_detail.reference}`;
|
||||
|
||||
return renderLink(ref, `/order/sales-order/${row.order}/`);
|
||||
}
|
||||
|
@ -974,9 +974,7 @@ function loadPartPurchaseOrderTable(table, part_id, options={}) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
var ref = global_settings.PURCHASEORDER_REFERENCE_PREFIX + order.reference;
|
||||
|
||||
var html = renderLink(ref, `/order/purchase-order/${order.pk}/`);
|
||||
var html = renderLink(order.reference, `/order/purchase-order/${order.pk}/`);
|
||||
|
||||
html += purchaseOrderStatusDisplay(
|
||||
order.status,
|
||||
|
@ -1916,10 +1916,7 @@ function loadStockTable(table, options) {
|
||||
var text = `${row.purchase_order}`;
|
||||
|
||||
if (row.purchase_order_reference) {
|
||||
|
||||
var prefix = global_settings.PURCHASEORDER_REFERENCE_PREFIX;
|
||||
|
||||
text = prefix + row.purchase_order_reference;
|
||||
text = row.purchase_order_reference;
|
||||
}
|
||||
|
||||
return renderLink(text, link);
|
||||
|
@ -63,6 +63,10 @@ function getAvailableTableFilters(tableKey) {
|
||||
type: 'bool',
|
||||
title: '{% trans "Has Available Stock" %}',
|
||||
},
|
||||
on_order: {
|
||||
type: 'bool',
|
||||
title: '{% trans "On Order" %}',
|
||||
},
|
||||
validated: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Validated" %}',
|
||||
|
@ -71,7 +71,7 @@ django-allauth==0.51.0
|
||||
# via
|
||||
# -r requirements.in
|
||||
# django-allauth-2fa
|
||||
django-allauth-2fa==0.9
|
||||
django-allauth-2fa==0.10.0
|
||||
# via -r requirements.in
|
||||
django-cleanup==6.0.0
|
||||
# via -r requirements.in
|
||||
|
Loading…
x
Reference in New Issue
Block a user