Merge remote-tracking branch 'upstream/master' into django-q

# Conflicts:
#	InvenTree/InvenTree/status.py
#	InvenTree/templates/about.html
This commit is contained in:
Oliver Walters 2021-04-07 22:29:47 +10:00
commit 9c38d67b52
76 changed files with 4616 additions and 2335 deletions

View File

@ -7,7 +7,7 @@ from __future__ import unicode_literals
import logging
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from django.http import JsonResponse
from django_filters.rest_framework import DjangoFilterBackend

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
from .validators import allowable_url_schemes
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from django.forms.fields import URLField as FormURLField
from django.db import models as models
@ -42,6 +42,7 @@ class DatePickerFormField(forms.DateField):
def __init__(self, **kwargs):
help_text = kwargs.get('help_text', _('Enter date'))
label = kwargs.get('label', None)
required = kwargs.get('required', False)
initial = kwargs.get('initial', None)
@ -56,7 +57,8 @@ class DatePickerFormField(forms.DateField):
required=required,
initial=initial,
help_text=help_text,
widget=widget
widget=widget,
label=label
)

View File

@ -5,7 +5,7 @@ Helper forms which subclass Django forms to provide additional functionality
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from django import forms
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field
@ -123,6 +123,7 @@ class DeleteForm(forms.Form):
confirm_delete = forms.BooleanField(
required=False,
initial=False,
label=_('Confirm delete'),
help_text=_('Confirm item deletion')
)
@ -155,6 +156,7 @@ class SetPasswordForm(HelperForm):
required=True,
initial='',
widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
label=_('Enter password'),
help_text=_('Enter new password'))
confirm_password = forms.CharField(max_length=100,
@ -162,6 +164,7 @@ class SetPasswordForm(HelperForm):
required=True,
initial='',
widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
label=_('Confirm password'),
help_text=_('Confirm new password'))
class Meta:

View File

@ -13,7 +13,7 @@ from decimal import Decimal
from wsgiref.util import FileWrapper
from django.http import StreamingHttpResponse
from django.core.exceptions import ValidationError, FieldError
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import Permission
@ -382,17 +382,17 @@ def extract_serial_numbers(serials, expected_quantity):
if a < b:
for n in range(a, b + 1):
if n in numbers:
errors.append(_('Duplicate serial: {n}'.format(n=n)))
errors.append(_('Duplicate serial: {n}').format(n=n))
else:
numbers.append(n)
else:
errors.append(_("Invalid group: {g}".format(g=group)))
errors.append(_("Invalid group: {g}").format(g=group))
except ValueError:
errors.append(_("Invalid group: {g}".format(g=group)))
errors.append(_("Invalid group: {g}").format(g=group))
continue
else:
errors.append(_("Invalid group: {g}".format(g=group)))
errors.append(_("Invalid group: {g}").format(g=group))
continue
else:
@ -409,7 +409,7 @@ def extract_serial_numbers(serials, expected_quantity):
# The number of extracted serial numbers must match the expected quantity
if not expected_quantity == len(numbers):
raise ValidationError([_("Number of unique serial number ({s}) must match quantity ({q})".format(s=len(numbers), q=expected_quantity))])
raise ValidationError([_("Number of unique serial number ({s}) must match quantity ({q})").format(s=len(numbers), q=expected_quantity)])
return numbers

View File

@ -56,19 +56,20 @@ class InvenTreeAttachment(models.Model):
def __str__(self):
return os.path.basename(self.attachment.name)
attachment = models.FileField(upload_to=rename_attachment,
attachment = models.FileField(upload_to=rename_attachment, verbose_name=_('Attachment'),
help_text=_('Select file to attach'))
comment = models.CharField(blank=True, max_length=100, help_text=_('File comment'))
comment = models.CharField(blank=True, max_length=100, verbose_name=_('Comment'), help_text=_('File comment'))
user = models.ForeignKey(
User,
on_delete=models.SET_NULL,
blank=True, null=True,
verbose_name=_('User'),
help_text=_('User'),
)
upload_date = models.DateField(auto_now_add=True, null=True, blank=True)
upload_date = models.DateField(auto_now_add=True, null=True, blank=True, verbose_name=_('upload date'))
@property
def basename(self):
@ -103,12 +104,14 @@ class InvenTreeTree(MPTTModel):
blank=False,
max_length=100,
validators=[validate_tree_name],
verbose_name=_("Name"),
help_text=_("Name"),
)
description = models.CharField(
blank=True,
max_length=250,
verbose_name=_("Description"),
help_text=_("Description (optional)")
)
@ -117,6 +120,7 @@ class InvenTreeTree(MPTTModel):
on_delete=models.DO_NOTHING,
blank=True,
null=True,
verbose_name=_("parent"),
related_name='children')
@property

View File

@ -3,13 +3,11 @@ Provides system status functionality checks.
"""
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
import logging
from datetime import datetime, timedelta
from django.utils.translation import ugettext as _
from django_q.models import Success
from django_q.monitor import Stat

View File

@ -1,4 +1,4 @@
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
class StatusCode:

View File

@ -110,6 +110,7 @@ dynamic_javascript_urls = [
url(r'^stock.js', DynamicJsView.as_view(template_name='js/stock.js'), name='stock.js'),
url(r'^tables.js', DynamicJsView.as_view(template_name='js/tables.js'), name='tables.js'),
url(r'^table_filters.js', DynamicJsView.as_view(template_name='js/table_filters.js'), name='table_filters.js'),
url(r'^filters.js', DynamicJsView.as_view(template_name='js/filters.js'), name='filters.js'),
]
urlpatterns = [

View File

@ -60,7 +60,7 @@ def validate_part_ipn(value):
match = re.search(pattern, value)
if match is None:
raise ValidationError(_('IPN must match regex pattern') + " '{pat}'".format(pat=pattern))
raise ValidationError(_('IPN must match regex pattern {pat}').format(pat=pattern))
def validate_build_order_reference(value):

View File

@ -71,7 +71,7 @@ def inventreeCommitHash():
try:
return str(subprocess.check_output('git rev-parse --short HEAD'.split()), 'utf-8').strip()
except FileNotFoundError:
except:
return None
@ -81,5 +81,5 @@ def inventreeCommitDate():
try:
d = str(subprocess.check_output('git show -s --format=%ci'.split()), 'utf-8').strip()
return d.split(' ')[0]
except FileNotFoundError:
except:
return None

View File

@ -2,7 +2,7 @@
from django.urls import reverse
from django.conf.urls import url
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from rest_framework.exceptions import ValidationError
from rest_framework import permissions

View File

@ -5,7 +5,7 @@ Django Forms for interacting with Build objects
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from django import forms
from InvenTree.forms import HelperForm
@ -36,11 +36,13 @@ class EditBuildForm(HelperForm):
}
target_date = DatePickerFormField(
label=_('Target Date'),
help_text=_('Target date for build completion. Build will be overdue after this date.')
)
quantity = RoundingDecimalFormField(
max_digits=10, decimal_places=5,
label=_('Quantity'),
help_text=_('Number of items to build')
)
@ -87,7 +89,7 @@ class BuildOutputCreateForm(HelperForm):
)
serial_numbers = forms.CharField(
label=_('Serial numbers'),
label=_('Serial Numbers'),
required=False,
help_text=_('Enter serial numbers for build outputs'),
)
@ -115,6 +117,7 @@ class BuildOutputDeleteForm(HelperForm):
confirm = forms.BooleanField(
required=False,
label=_('Confirm'),
help_text=_('Confirm deletion of build output')
)
@ -136,7 +139,7 @@ class UnallocateBuildForm(HelperForm):
Form for auto-de-allocation of stock from a build
"""
confirm = forms.BooleanField(required=False, help_text=_('Confirm unallocation of stock'))
confirm = forms.BooleanField(required=False, label=_('Confirm'), help_text=_('Confirm unallocation of stock'))
output_id = forms.IntegerField(
required=False,
@ -160,7 +163,7 @@ class UnallocateBuildForm(HelperForm):
class AutoAllocateForm(HelperForm):
""" Form for auto-allocation of stock to a build """
confirm = forms.BooleanField(required=True, help_text=_('Confirm stock allocation'))
confirm = forms.BooleanField(required=True, label=_('Confirm'), help_text=_('Confirm stock allocation'))
# Keep track of which build output we are interested in
output = forms.ModelChoiceField(
@ -207,15 +210,17 @@ class CompleteBuildOutputForm(HelperForm):
location = forms.ModelChoiceField(
queryset=StockLocation.objects.all(),
label=_('Location'),
help_text=_('Location of completed parts'),
)
confirm_incomplete = forms.BooleanField(
required=False,
label=_('Confirm incomplete'),
help_text=_("Confirm completion with incomplete stock allocation")
)
confirm = forms.BooleanField(required=True, help_text=_('Confirm build completion'))
confirm = forms.BooleanField(required=True, label=_('Confirm'), help_text=_('Confirm build completion'))
output = forms.ModelChoiceField(
queryset=StockItem.objects.all(), # Queryset is narrowed in the view
@ -235,7 +240,7 @@ class CompleteBuildOutputForm(HelperForm):
class CancelBuildForm(HelperForm):
""" Form for cancelling a build """
confirm_cancel = forms.BooleanField(required=False, help_text=_('Confirm build cancellation'))
confirm_cancel = forms.BooleanField(required=False, label=_('Confirm cancel'), help_text=_('Confirm build cancellation'))
class Meta:
model = Build
@ -249,7 +254,7 @@ class EditBuildItemForm(HelperForm):
Form for creating (or editing) a BuildItem object.
"""
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, help_text=_('Select quantity of stock to allocate'))
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity'), help_text=_('Select quantity of stock to allocate'))
part_id = forms.IntegerField(required=False, widget=forms.HiddenInput())

View File

@ -0,0 +1,85 @@
# Generated by Django 3.0.7 on 2021-04-04 20:16
import InvenTree.models
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('stock', '0058_stockitem_packaging'),
('users', '0005_owner_model'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('build', '0026_auto_20210216_1539'),
]
operations = [
migrations.AlterField(
model_name='build',
name='completed_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds_completed', to=settings.AUTH_USER_MODEL, verbose_name='completed by'),
),
migrations.AlterField(
model_name='build',
name='completion_date',
field=models.DateField(blank=True, null=True, verbose_name='Completion Date'),
),
migrations.AlterField(
model_name='build',
name='creation_date',
field=models.DateField(auto_now_add=True, verbose_name='Creation Date'),
),
migrations.AlterField(
model_name='build',
name='issued_by',
field=models.ForeignKey(blank=True, help_text='User who issued this build order', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds_issued', to=settings.AUTH_USER_MODEL, verbose_name='Issued by'),
),
migrations.AlterField(
model_name='build',
name='responsible',
field=models.ForeignKey(blank=True, help_text='User responsible for this build order', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds_responsible', to='users.Owner', verbose_name='Responsible'),
),
migrations.AlterField(
model_name='builditem',
name='build',
field=models.ForeignKey(help_text='Build to allocate parts', on_delete=django.db.models.deletion.CASCADE, related_name='allocated_stock', to='build.Build', verbose_name='Build'),
),
migrations.AlterField(
model_name='builditem',
name='install_into',
field=models.ForeignKey(blank=True, help_text='Destination stock item', limit_choices_to={'is_building': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='items_to_install', to='stock.StockItem', verbose_name='Install into'),
),
migrations.AlterField(
model_name='builditem',
name='quantity',
field=models.DecimalField(decimal_places=5, default=1, help_text='Stock quantity to allocate to build', max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity'),
),
migrations.AlterField(
model_name='builditem',
name='stock_item',
field=models.ForeignKey(help_text='Source stock item', limit_choices_to={'belongs_to': None, 'sales_order': None}, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='stock.StockItem', verbose_name='Stock Item'),
),
migrations.AlterField(
model_name='buildorderattachment',
name='attachment',
field=models.FileField(help_text='Select file to attach', upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'),
),
migrations.AlterField(
model_name='buildorderattachment',
name='comment',
field=models.CharField(blank=True, help_text='File comment', max_length=100, verbose_name='Comment'),
),
migrations.AlterField(
model_name='buildorderattachment',
name='upload_date',
field=models.DateField(auto_now_add=True, null=True, verbose_name='upload date'),
),
migrations.AlterField(
model_name='buildorderattachment',
name='user',
field=models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
]

View File

@ -9,7 +9,7 @@ import os
from datetime import datetime
from django.contrib.auth.models import User
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from django.urls import reverse
@ -216,7 +216,7 @@ class Build(MPTTModel):
help_text=_('Batch code for this build output')
)
creation_date = models.DateField(auto_now_add=True, editable=False)
creation_date = models.DateField(auto_now_add=True, editable=False, verbose_name=_('Creation Date'))
target_date = models.DateField(
null=True, blank=True,
@ -224,12 +224,13 @@ class Build(MPTTModel):
help_text=_('Target date for build completion. Build will be overdue after this date.')
)
completion_date = models.DateField(null=True, blank=True)
completion_date = models.DateField(null=True, blank=True, verbose_name=_('Completion Date'))
completed_by = models.ForeignKey(
User,
on_delete=models.SET_NULL,
blank=True, null=True,
verbose_name=_('completed by'),
related_name='builds_completed'
)
@ -237,6 +238,7 @@ class Build(MPTTModel):
User,
on_delete=models.SET_NULL,
blank=True, null=True,
verbose_name=_('Issued by'),
help_text=_('User who issued this build order'),
related_name='builds_issued',
)
@ -245,6 +247,7 @@ class Build(MPTTModel):
UserModels.Owner,
on_delete=models.SET_NULL,
blank=True, null=True,
verbose_name=_('Responsible'),
help_text=_('User responsible for this build order'),
related_name='builds_responsible',
)
@ -1017,14 +1020,14 @@ class BuildItem(models.Model):
try:
# Allocated part must be in the BOM for the master part
if self.stock_item.part not in self.build.part.getRequiredParts(recursive=False):
errors['stock_item'] = [_("Selected stock item not found in BOM for part '{p}'".format(p=self.build.part.full_name))]
errors['stock_item'] = [_("Selected stock item not found in BOM for part '{p}'").format(p=self.build.part.full_name)]
# Allocated quantity cannot exceed available stock quantity
if self.quantity > self.stock_item.quantity:
errors['quantity'] = [_("Allocated quantity ({n}) must not exceed available quantity ({q})".format(
errors['quantity'] = [_("Allocated quantity ({n}) must not exceed available quantity ({q})").format(
n=normalize(self.quantity),
q=normalize(self.stock_item.quantity)
))]
)]
# Allocated quantity cannot cause the stock item to be over-allocated
if self.stock_item.quantity - self.stock_item.allocation_count() + self.quantity < self.quantity:
@ -1076,6 +1079,7 @@ class BuildItem(models.Model):
Build,
on_delete=models.CASCADE,
related_name='allocated_stock',
verbose_name=_('Build'),
help_text=_('Build to allocate parts')
)
@ -1083,6 +1087,7 @@ class BuildItem(models.Model):
'stock.StockItem',
on_delete=models.CASCADE,
related_name='allocations',
verbose_name=_('Stock Item'),
help_text=_('Source stock item'),
limit_choices_to={
'sales_order': None,
@ -1095,6 +1100,7 @@ class BuildItem(models.Model):
max_digits=15,
default=1,
validators=[MinValueValidator(0)],
verbose_name=_('Quantity'),
help_text=_('Stock quantity to allocate to build')
)
@ -1103,6 +1109,7 @@ class BuildItem(models.Model):
on_delete=models.SET_NULL,
blank=True, null=True,
related_name='items_to_install',
verbose_name=_('Install into'),
help_text=_('Destination stock item'),
limit_choices_to={
'is_building': True,

View File

@ -164,7 +164,7 @@ src="{% static 'img/blank_image.png' %}"
launchModalForm("{% url 'build-cancel' build.id %}",
{
reload: true,
submit_text: "Cancel Build",
submit_text: '{% trans "Cancel Build" %}',
});
});
@ -173,7 +173,7 @@ src="{% static 'img/blank_image.png' %}"
"{% url 'build-complete' build.id %}",
{
reload: true,
submit_text: "Complete Build",
submit_text: '{% trans "Complete Build" %}',
}
);
});

View File

@ -130,6 +130,7 @@ InvenTree | {% trans "Build Orders" %}
initialView: 'dayGridMonth',
nowIndicator: true,
aspectRatio: 2.5,
locale: '{{request.LANGUAGE_CODE}}',
datesSet: function() {
loadOrderEvents(calendar);
}

View File

@ -5,7 +5,7 @@ Django views for interacting with Build objects
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from django.views.generic import DetailView, ListView, UpdateView
from django.forms import HiddenInput

View File

@ -17,7 +17,7 @@ from djmoney.models.fields import MoneyField
from djmoney.contrib.exchange.models import convert_money
from djmoney.contrib.exchange.exceptions import MissingRate
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator, URLValidator
from django.core.exceptions import ValidationError

View File

@ -5,7 +5,7 @@ Django views for interacting with common models
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from django.forms import CheckboxInput, Select
from InvenTree.views import AjaxUpdateView

View File

@ -8,7 +8,7 @@ from __future__ import unicode_literals
from InvenTree.forms import HelperForm
from InvenTree.fields import RoundingDecimalFormField
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
import django.forms
import djmoney.settings
@ -34,6 +34,7 @@ class EditCompanyForm(HelperForm):
currency = django.forms.ChoiceField(
required=False,
label=_('Currency'),
help_text=_('Default currency used for this company'),
choices=[('', '----------')] + djmoney.settings.CURRENCY_CHOICES,
initial=common.settings.currency_code_default,

View File

@ -0,0 +1,69 @@
# Generated by Django 3.0.7 on 2021-04-03 18:37
import InvenTree.fields
import company.models
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import markdownx.models
import stdimage.models
class Migration(migrations.Migration):
dependencies = [
('company', '0031_auto_20210103_2215'),
]
operations = [
migrations.AlterField(
model_name='company',
name='image',
field=stdimage.models.StdImageField(blank=True, null=True, upload_to=company.models.rename_company_image, verbose_name='Image'),
),
migrations.AlterField(
model_name='company',
name='is_customer',
field=models.BooleanField(default=False, help_text='Do you sell items to this company?', verbose_name='is customer'),
),
migrations.AlterField(
model_name='company',
name='is_manufacturer',
field=models.BooleanField(default=False, help_text='Does this company manufacture parts?', verbose_name='is manufacturer'),
),
migrations.AlterField(
model_name='company',
name='is_supplier',
field=models.BooleanField(default=True, help_text='Do you purchase items from this company?', verbose_name='is supplier'),
),
migrations.AlterField(
model_name='company',
name='link',
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external company information', verbose_name='Link'),
),
migrations.AlterField(
model_name='company',
name='notes',
field=markdownx.models.MarkdownxField(blank=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='supplierpart',
name='base_cost',
field=models.DecimalField(decimal_places=3, default=0, help_text='Minimum charge (e.g. stocking fee)', max_digits=10, validators=[django.core.validators.MinValueValidator(0)], verbose_name='base cost'),
),
migrations.AlterField(
model_name='supplierpart',
name='multiple',
field=models.PositiveIntegerField(default=1, help_text='Order multiple', validators=[django.core.validators.MinValueValidator(1)], verbose_name='multiple'),
),
migrations.AlterField(
model_name='supplierpart',
name='packaging',
field=models.CharField(blank=True, help_text='Part packaging', max_length=50, null=True, verbose_name='Packaging'),
),
migrations.AlterField(
model_name='supplierpricebreak',
name='part',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pricebreaks', to='company.SupplierPart', verbose_name='Part'),
),
]

View File

@ -9,7 +9,7 @@ import os
import math
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models import Sum, Q, UniqueConstraint
@ -114,7 +114,7 @@ class Company(models.Model):
verbose_name=_('Contact'),
blank=True, help_text=_('Point of contact'))
link = InvenTreeURLField(blank=True, help_text=_('Link to external company information'))
link = InvenTreeURLField(blank=True, verbose_name=_('Link'), help_text=_('Link to external company information'))
image = StdImageField(
upload_to=rename_company_image,
@ -122,15 +122,16 @@ class Company(models.Model):
blank=True,
variations={'thumbnail': (128, 128)},
delete_orphans=True,
verbose_name=_('Image'),
)
notes = MarkdownxField(blank=True)
notes = MarkdownxField(blank=True, verbose_name=_('Notes'))
is_customer = models.BooleanField(default=False, help_text=_('Do you sell items to this company?'))
is_customer = models.BooleanField(default=False, verbose_name=_('is customer'), help_text=_('Do you sell items to this company?'))
is_supplier = models.BooleanField(default=True, help_text=_('Do you purchase items from this company?'))
is_supplier = models.BooleanField(default=True, verbose_name=_('is supplier'), help_text=_('Do you purchase items from this company?'))
is_manufacturer = models.BooleanField(default=False, help_text=_('Does this company manufacture parts?'))
is_manufacturer = models.BooleanField(default=False, verbose_name=_('is manufacturer'), help_text=_('Does this company manufacture parts?'))
currency = models.CharField(
max_length=3,
@ -366,11 +367,11 @@ class SupplierPart(models.Model):
help_text=_('Notes')
)
base_cost = models.DecimalField(max_digits=10, decimal_places=3, default=0, validators=[MinValueValidator(0)], help_text=_('Minimum charge (e.g. stocking fee)'))
base_cost = models.DecimalField(max_digits=10, decimal_places=3, default=0, validators=[MinValueValidator(0)], verbose_name=_('base cost'), help_text=_('Minimum charge (e.g. stocking fee)'))
packaging = models.CharField(max_length=50, blank=True, null=True, help_text=_('Part packaging'))
packaging = models.CharField(max_length=50, blank=True, null=True, verbose_name=_('Packaging'), help_text=_('Part packaging'))
multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)], help_text=('Order multiple'))
multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)], verbose_name=_('multiple'), help_text=_('Order multiple'))
# TODO - Reimplement lead-time as a charfield with special validation (pattern matching).
# lead_time = models.DurationField(blank=True, null=True)
@ -530,7 +531,7 @@ class SupplierPriceBreak(common.models.PriceBreak):
currency: Reference to the currency of this pricebreak (leave empty for base currency)
"""
part = models.ForeignKey(SupplierPart, on_delete=models.CASCADE, related_name='pricebreaks')
part = models.ForeignKey(SupplierPart, on_delete=models.CASCADE, related_name='pricebreaks', verbose_name=_('Part'),)
class Meta:
unique_together = ("part", "quantity")

View File

@ -43,17 +43,17 @@ InvenTree | {% trans "Company" %} - {{ company.name }}
<p>{{ company.description }}</p>
<div class='btn-group action-buttons'>
{% if company.is_supplier and roles.purchase_order.add %}
<button type='button' class='btn btn-default' id='company-order-2' title='Create purchase order'>
<button type='button' class='btn btn-default' id='company-order-2' title='{% trans "Create Purchase Order" %}'>
<span class='fas fa-shopping-cart'/>
</button>
{% endif %}
{% if perms.company.change_company %}
<button type='button' class='btn btn-default' id='company-edit' title='Edit company information'>
<button type='button' class='btn btn-default' id='company-edit' title='{% trans "Edit company information" %}'>
<span class='fas fa-edit icon-green'/>
</button>
{% endif %}
{% if perms.company.delete_company %}
<button type='button' class='btn btn-default' id='company-delete' title='Delete company'>
<button type='button' class='btn btn-default' id='company-delete' title='{% trans "Delete Company" %}'>
<span class='fas fa-trash-alt icon-red'/>
</button>
{% endif %}

View File

@ -1,14 +1,16 @@
{% extends "modal_delete_form.html" %}
{% load i18n %}
{% block pre_form_content %}
Are you sure you want to delete company '{{ company.name }}'?
{% blocktrans with company.name as name %}Are you sure you want to delete company '{{ name }}'?{% endblocktrans %}
<br>
{% if company.supplied_part_count > 0 %}
<p>There are {{ company.supplied_part_count }} parts sourced from this company.<br>
If this supplier is deleted, these supplier part entries will also be deleted.</p>
<p>{% blocktrans with company.supplied_part_count as count %}There are {{ count }} parts sourced from this company.<br>
If this supplier is deleted, these supplier part entries will also be deleted.{% endblocktrans %}</p>
<ul class='list-group'>
{% for part in company.parts.all %}
<li class='list-group-item'><b>{{ part.SKU }}</b> - <i>{{ part.part.full_name }}</i></li>

View File

@ -15,7 +15,7 @@
{% if roles.purchase_order.change %}
<div id='button-toolbar'>
<div class='button-toolbar container-fluid'>
<div class='btn-group role='group'>
<div class='btn-group' role='group'>
{% if roles.purchase_order.add %}
<button class="btn btn-success" id='part-create' title='{% trans "Create new supplier part" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Supplier Part" %}

View File

@ -14,7 +14,7 @@
{% if roles.purchase_order.add %}
<div id='button-bar'>
<div class='btn-group'>
<button class='btn btn-primary' type='button' id='order-part2' title='Order part'>
<button class='btn btn-primary' type='button' id='order-part2' title='{% trans "Order part" %}'>
<span class='fas fa-shopping-cart'></span> {% trans "Order Part" %}</button>
</div>
</div>

View File

@ -6,7 +6,7 @@ Django views for interacting with Company app
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from django.views.generic import DetailView, ListView, UpdateView
from django.urls import reverse

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from django.conf.urls import url, include
from django.core.exceptions import ValidationError, FieldError
from django.http import HttpResponse

View File

@ -126,7 +126,7 @@ class LabelTemplate(models.Model):
width = models.FloatField(
default=50,
verbose_name=('Width [mm]'),
verbose_name=_('Width [mm]'),
help_text=_('Label width, specified in mm'),
validators=[MinValueValidator(2)]
)

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

View File

@ -6,7 +6,7 @@ Django Forms for interacting with Order objects
from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from mptt.fields import TreeNodeChoiceField
@ -24,7 +24,7 @@ from .models import SalesOrderAllocation
class IssuePurchaseOrderForm(HelperForm):
confirm = forms.BooleanField(required=True, initial=False, help_text=_('Place order'))
confirm = forms.BooleanField(required=True, initial=False, label=_('Confirm'), help_text=_('Place order'))
class Meta:
model = PurchaseOrder
@ -35,7 +35,7 @@ class IssuePurchaseOrderForm(HelperForm):
class CompletePurchaseOrderForm(HelperForm):
confirm = forms.BooleanField(required=True, help_text=_("Mark order as complete"))
confirm = forms.BooleanField(required=True, label=_('Confirm'), help_text=_("Mark order as complete"))
class Meta:
model = PurchaseOrder
@ -46,7 +46,7 @@ class CompletePurchaseOrderForm(HelperForm):
class CancelPurchaseOrderForm(HelperForm):
confirm = forms.BooleanField(required=True, help_text=_('Cancel order'))
confirm = forms.BooleanField(required=True, label=_('Confirm'), help_text=_('Cancel order'))
class Meta:
model = PurchaseOrder
@ -57,7 +57,7 @@ class CancelPurchaseOrderForm(HelperForm):
class CancelSalesOrderForm(HelperForm):
confirm = forms.BooleanField(required=True, help_text=_('Cancel order'))
confirm = forms.BooleanField(required=True, label=_('Confirm'), help_text=_('Cancel order'))
class Meta:
model = SalesOrder
@ -68,7 +68,7 @@ class CancelSalesOrderForm(HelperForm):
class ShipSalesOrderForm(HelperForm):
confirm = forms.BooleanField(required=True, help_text=_('Ship order'))
confirm = forms.BooleanField(required=True, label=_('Confirm'), help_text=_('Ship order'))
class Meta:
model = SalesOrder
@ -79,7 +79,7 @@ class ShipSalesOrderForm(HelperForm):
class ReceivePurchaseOrderForm(HelperForm):
location = TreeNodeChoiceField(queryset=StockLocation.objects.all(), required=True, help_text=_('Receive parts to this location'))
location = TreeNodeChoiceField(queryset=StockLocation.objects.all(), required=True, label=_('Location'), help_text=_('Receive parts to this location'))
class Meta:
model = PurchaseOrder
@ -106,6 +106,7 @@ class EditPurchaseOrderForm(HelperForm):
super().__init__(*args, **kwargs)
target_date = DatePickerFormField(
label=_('Target Date'),
help_text=_('Target date for order delivery. Order will be overdue after this date.'),
)
@ -140,6 +141,7 @@ class EditSalesOrderForm(HelperForm):
super().__init__(*args, **kwargs)
target_date = DatePickerFormField(
label=_('Target Date'),
help_text=_('Target date for order completion. Order will be overdue after this date.'),
)
@ -183,7 +185,7 @@ class EditSalesOrderAttachmentForm(HelperForm):
class EditPurchaseOrderLineItemForm(HelperForm):
""" Form for editing a PurchaseOrderLineItem object """
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5)
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity'))
class Meta:
model = PurchaseOrderLineItem
@ -200,7 +202,7 @@ class EditPurchaseOrderLineItemForm(HelperForm):
class EditSalesOrderLineItemForm(HelperForm):
""" Form for editing a SalesOrderLineItem object """
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5)
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity'))
class Meta:
model = SalesOrderLineItem
@ -256,7 +258,7 @@ class CreateSalesOrderAllocationForm(HelperForm):
Form for creating a SalesOrderAllocation item.
"""
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5)
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity'))
class Meta:
model = SalesOrderAllocation
@ -273,7 +275,7 @@ class EditSalesOrderAllocationForm(HelperForm):
Form for editing a SalesOrderAllocation item
"""
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5)
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity'))
class Meta:
model = SalesOrderAllocation

View File

@ -0,0 +1,233 @@
# Generated by Django 3.0.7 on 2021-04-04 20:16
import InvenTree.fields
import InvenTree.models
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import markdownx.models
class Migration(migrations.Migration):
dependencies = [
('company', '0032_auto_20210403_1837'),
('part', '0063_bomitem_inherited'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('stock', '0058_stockitem_packaging'),
('order', '0043_auto_20210330_0013'),
]
operations = [
migrations.AlterField(
model_name='purchaseorder',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Created By'),
),
migrations.AlterField(
model_name='purchaseorder',
name='creation_date',
field=models.DateField(blank=True, null=True, verbose_name='Creation Date'),
),
migrations.AlterField(
model_name='purchaseorder',
name='description',
field=models.CharField(help_text='Order description', max_length=250, verbose_name='Description'),
),
migrations.AlterField(
model_name='purchaseorder',
name='link',
field=models.URLField(blank=True, help_text='Link to external page', verbose_name='Link'),
),
migrations.AlterField(
model_name='purchaseorder',
name='notes',
field=markdownx.models.MarkdownxField(blank=True, help_text='Order notes', verbose_name='Notes'),
),
migrations.AlterField(
model_name='purchaseorder',
name='received_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='received by'),
),
migrations.AlterField(
model_name='purchaseorder',
name='reference',
field=models.CharField(help_text='Order reference', max_length=64, unique=True, verbose_name='Reference'),
),
migrations.AlterField(
model_name='purchaseorder',
name='supplier',
field=models.ForeignKey(help_text='Company from which the items are being ordered', limit_choices_to={'is_supplier': True}, on_delete=django.db.models.deletion.CASCADE, related_name='purchase_orders', to='company.Company', verbose_name='Supplier'),
),
migrations.AlterField(
model_name='purchaseorder',
name='supplier_reference',
field=models.CharField(blank=True, help_text='Supplier order reference code', max_length=64, verbose_name='Supplier Reference'),
),
migrations.AlterField(
model_name='purchaseorderattachment',
name='attachment',
field=models.FileField(help_text='Select file to attach', upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'),
),
migrations.AlterField(
model_name='purchaseorderattachment',
name='comment',
field=models.CharField(blank=True, help_text='File comment', max_length=100, verbose_name='Comment'),
),
migrations.AlterField(
model_name='purchaseorderattachment',
name='upload_date',
field=models.DateField(auto_now_add=True, null=True, verbose_name='upload date'),
),
migrations.AlterField(
model_name='purchaseorderattachment',
name='user',
field=models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
migrations.AlterField(
model_name='purchaseorderlineitem',
name='notes',
field=models.CharField(blank=True, help_text='Line item notes', max_length=500, verbose_name='Notes'),
),
migrations.AlterField(
model_name='purchaseorderlineitem',
name='order',
field=models.ForeignKey(help_text='Purchase Order', on_delete=django.db.models.deletion.CASCADE, related_name='lines', to='order.PurchaseOrder', verbose_name='Order'),
),
migrations.AlterField(
model_name='purchaseorderlineitem',
name='part',
field=models.ForeignKey(blank=True, help_text='Supplier part', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='purchase_order_line_items', to='company.SupplierPart', verbose_name='Part'),
),
migrations.AlterField(
model_name='purchaseorderlineitem',
name='quantity',
field=InvenTree.fields.RoundingDecimalField(decimal_places=5, default=1, help_text='Item quantity', max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity'),
),
migrations.AlterField(
model_name='purchaseorderlineitem',
name='received',
field=models.DecimalField(decimal_places=5, default=0, help_text='Number of items received', max_digits=15, verbose_name='Received'),
),
migrations.AlterField(
model_name='purchaseorderlineitem',
name='reference',
field=models.CharField(blank=True, help_text='Line item reference', max_length=100, verbose_name='Reference'),
),
migrations.AlterField(
model_name='salesorder',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Created By'),
),
migrations.AlterField(
model_name='salesorder',
name='creation_date',
field=models.DateField(blank=True, null=True, verbose_name='Creation Date'),
),
migrations.AlterField(
model_name='salesorder',
name='customer',
field=models.ForeignKey(help_text='Company to which the items are being sold', limit_choices_to={'is_customer': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sales_orders', to='company.Company', verbose_name='Customer'),
),
migrations.AlterField(
model_name='salesorder',
name='customer_reference',
field=models.CharField(blank=True, help_text='Customer order reference code', max_length=64, verbose_name='Customer Reference '),
),
migrations.AlterField(
model_name='salesorder',
name='description',
field=models.CharField(help_text='Order description', max_length=250, verbose_name='Description'),
),
migrations.AlterField(
model_name='salesorder',
name='link',
field=models.URLField(blank=True, help_text='Link to external page', verbose_name='Link'),
),
migrations.AlterField(
model_name='salesorder',
name='notes',
field=markdownx.models.MarkdownxField(blank=True, help_text='Order notes', verbose_name='Notes'),
),
migrations.AlterField(
model_name='salesorder',
name='reference',
field=models.CharField(help_text='Order reference', max_length=64, unique=True, verbose_name='Reference'),
),
migrations.AlterField(
model_name='salesorder',
name='shipment_date',
field=models.DateField(blank=True, null=True, verbose_name='Shipment Date'),
),
migrations.AlterField(
model_name='salesorder',
name='shipped_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='shipped by'),
),
migrations.AlterField(
model_name='salesorder',
name='status',
field=models.PositiveIntegerField(choices=[(10, 'Pending'), (20, 'Shipped'), (40, 'Cancelled'), (50, 'Lost'), (60, 'Returned')], default=10, help_text='Purchase order status', verbose_name='Status'),
),
migrations.AlterField(
model_name='salesorderallocation',
name='item',
field=models.ForeignKey(help_text='Select stock item to allocate', limit_choices_to={'belongs_to': None, 'part__salable': True, 'sales_order': None}, on_delete=django.db.models.deletion.CASCADE, related_name='sales_order_allocations', to='stock.StockItem', verbose_name='Item'),
),
migrations.AlterField(
model_name='salesorderallocation',
name='line',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='order.SalesOrderLineItem', verbose_name='Line'),
),
migrations.AlterField(
model_name='salesorderallocation',
name='quantity',
field=InvenTree.fields.RoundingDecimalField(decimal_places=5, default=1, help_text='Enter stock allocation quantity', max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity'),
),
migrations.AlterField(
model_name='salesorderattachment',
name='attachment',
field=models.FileField(help_text='Select file to attach', upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'),
),
migrations.AlterField(
model_name='salesorderattachment',
name='comment',
field=models.CharField(blank=True, help_text='File comment', max_length=100, verbose_name='Comment'),
),
migrations.AlterField(
model_name='salesorderattachment',
name='upload_date',
field=models.DateField(auto_now_add=True, null=True, verbose_name='upload date'),
),
migrations.AlterField(
model_name='salesorderattachment',
name='user',
field=models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
migrations.AlterField(
model_name='salesorderlineitem',
name='notes',
field=models.CharField(blank=True, help_text='Line item notes', max_length=500, verbose_name='Notes'),
),
migrations.AlterField(
model_name='salesorderlineitem',
name='order',
field=models.ForeignKey(help_text='Sales Order', on_delete=django.db.models.deletion.CASCADE, related_name='lines', to='order.SalesOrder', verbose_name='Order'),
),
migrations.AlterField(
model_name='salesorderlineitem',
name='part',
field=models.ForeignKey(help_text='Part', limit_choices_to={'salable': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sales_order_line_items', to='part.Part', verbose_name='Part'),
),
migrations.AlterField(
model_name='salesorderlineitem',
name='quantity',
field=InvenTree.fields.RoundingDecimalField(decimal_places=5, default=1, help_text='Item quantity', max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity'),
),
migrations.AlterField(
model_name='salesorderlineitem',
name='reference',
field=models.CharField(blank=True, help_text='Line item reference', max_length=100, verbose_name='Reference'),
),
]

View File

@ -15,7 +15,7 @@ from django.core.validators import MinValueValidator
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User
from django.urls import reverse
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from markdownx.models import MarkdownxField
@ -96,18 +96,19 @@ class Order(models.Model):
class Meta:
abstract = True
reference = models.CharField(unique=True, max_length=64, blank=False, help_text=_('Order reference'))
reference = models.CharField(unique=True, max_length=64, blank=False, verbose_name=_('Reference'), help_text=_('Order reference'))
description = models.CharField(max_length=250, help_text=_('Order description'))
description = models.CharField(max_length=250, verbose_name=_('Description'), help_text=_('Order description'))
link = models.URLField(blank=True, help_text=_('Link to external page'))
link = models.URLField(blank=True, verbose_name=_('Link'), help_text=_('Link to external page'))
creation_date = models.DateField(blank=True, null=True)
creation_date = models.DateField(blank=True, null=True, verbose_name=_('Creation Date'))
created_by = models.ForeignKey(User,
on_delete=models.SET_NULL,
blank=True, null=True,
related_name='+'
related_name='+',
verbose_name=_('Created By')
)
responsible = models.ForeignKey(
@ -119,7 +120,7 @@ class Order(models.Model):
related_name='+',
)
notes = MarkdownxField(blank=True, help_text=_('Order notes'))
notes = MarkdownxField(blank=True, verbose_name=_('Notes'), help_text=_('Order notes'))
class PurchaseOrder(Order):
@ -186,16 +187,18 @@ class PurchaseOrder(Order):
'is_supplier': True,
},
related_name='purchase_orders',
verbose_name=_('Supplier'),
help_text=_('Company from which the items are being ordered')
)
supplier_reference = models.CharField(max_length=64, blank=True, help_text=_("Supplier order reference code"))
supplier_reference = models.CharField(max_length=64, blank=True, verbose_name=_('Supplier Reference'), help_text=_("Supplier order reference code"))
received_by = models.ForeignKey(
User,
on_delete=models.SET_NULL,
blank=True, null=True,
related_name='+'
related_name='+',
verbose_name=_('received by')
)
issue_date = models.DateField(
@ -434,13 +437,14 @@ class SalesOrder(Order):
null=True,
limit_choices_to={'is_customer': True},
related_name='sales_orders',
verbose_name=_('Customer'),
help_text=_("Company to which the items are being sold"),
)
status = models.PositiveIntegerField(default=SalesOrderStatus.PENDING, choices=SalesOrderStatus.items(),
help_text=_('Purchase order status'))
verbose_name=_('Status'), help_text=_('Purchase order status'))
customer_reference = models.CharField(max_length=64, blank=True, help_text=_("Customer order reference code"))
customer_reference = models.CharField(max_length=64, blank=True, verbose_name=_('Customer Reference '), help_text=_("Customer order reference code"))
target_date = models.DateField(
null=True, blank=True,
@ -448,13 +452,14 @@ class SalesOrder(Order):
help_text=_('Target date for order completion. Order will be overdue after this date.')
)
shipment_date = models.DateField(blank=True, null=True)
shipment_date = models.DateField(blank=True, null=True, verbose_name=_('Shipment Date'))
shipped_by = models.ForeignKey(
User,
on_delete=models.SET_NULL,
blank=True, null=True,
related_name='+'
related_name='+',
verbose_name=_('shipped by')
)
@property
@ -585,11 +590,11 @@ class OrderLineItem(models.Model):
class Meta:
abstract = True
quantity = RoundingDecimalField(max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], default=1, help_text=_('Item quantity'))
quantity = RoundingDecimalField(max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], default=1, verbose_name=_('Quantity'), help_text=_('Item quantity'))
reference = models.CharField(max_length=100, blank=True, help_text=_('Line item reference'))
reference = models.CharField(max_length=100, blank=True, verbose_name=_('Reference'), help_text=_('Line item reference'))
notes = models.CharField(max_length=500, blank=True, help_text=_('Line item notes'))
notes = models.CharField(max_length=500, blank=True, verbose_name=_('Notes'), help_text=_('Line item notes'))
class PurchaseOrderLineItem(OrderLineItem):
@ -615,6 +620,7 @@ class PurchaseOrderLineItem(OrderLineItem):
order = models.ForeignKey(
PurchaseOrder, on_delete=models.CASCADE,
related_name='lines',
verbose_name=_('Order'),
help_text=_('Purchase Order')
)
@ -628,10 +634,11 @@ class PurchaseOrderLineItem(OrderLineItem):
SupplierPart, on_delete=models.SET_NULL,
blank=True, null=True,
related_name='purchase_order_line_items',
verbose_name=_('Part'),
help_text=_("Supplier part"),
)
received = models.DecimalField(decimal_places=5, max_digits=15, default=0, help_text=_('Number of items received'))
received = models.DecimalField(decimal_places=5, max_digits=15, default=0, verbose_name=_('Received'), help_text=_('Number of items received'))
purchase_price = MoneyField(
max_digits=19,
@ -657,9 +664,9 @@ class SalesOrderLineItem(OrderLineItem):
part: Link to a Part object (may be null)
"""
order = models.ForeignKey(SalesOrder, on_delete=models.CASCADE, related_name='lines', help_text=_('Sales Order'))
order = models.ForeignKey(SalesOrder, on_delete=models.CASCADE, related_name='lines', verbose_name=_('Order'), help_text=_('Sales Order'))
part = models.ForeignKey('part.Part', on_delete=models.SET_NULL, related_name='sales_order_line_items', null=True, help_text=_('Part'), limit_choices_to={'salable': True})
part = models.ForeignKey('part.Part', on_delete=models.SET_NULL, related_name='sales_order_line_items', null=True, verbose_name=_('Part'), help_text=_('Part'), limit_choices_to={'salable': True})
class Meta:
unique_together = [
@ -759,7 +766,7 @@ class SalesOrderAllocation(models.Model):
if len(errors) > 0:
raise ValidationError(errors)
line = models.ForeignKey(SalesOrderLineItem, on_delete=models.CASCADE, related_name='allocations')
line = models.ForeignKey(SalesOrderLineItem, on_delete=models.CASCADE, verbose_name=_('Line'), related_name='allocations')
item = models.ForeignKey(
'stock.StockItem',
@ -770,10 +777,11 @@ class SalesOrderAllocation(models.Model):
'belongs_to': None,
'sales_order': None,
},
verbose_name=_('Item'),
help_text=_('Select stock item to allocate')
)
quantity = RoundingDecimalField(max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], default=1, help_text=_('Enter stock allocation quantity'))
quantity = RoundingDecimalField(max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], default=1, verbose_name=_('Quantity'), help_text=_('Enter stock allocation quantity'))
def get_serial(self):
return self.item.serial

View File

@ -1,12 +1,14 @@
{% extends "modal_form.html" %}
{% load i18n %}
{% block pre_form_content %}
Mark this order as complete?
{% trans 'Mark this order as complete?' %}
{% if not order.is_complete %}
<div class='alert alert-warning alert-block'>
This order has line items which have not been marked as received.
Marking this order as complete will remove these line items.
{%trans 'This order has line items which have not been marked as received.
Marking this order as complete will remove these line items.' %}
</div>
{% endif %}

View File

@ -1,7 +1,9 @@
{% extends "modal_form.html" %}
{% load i18n %}
{% block pre_form_content %}
After placing this purchase order, line items will no longer be editable.
{% trans 'After placing this purchase order, line items will no longer be editable.' %}
{% endblock %}

View File

@ -39,7 +39,7 @@
{{ part.full_name }} <small><i>{{ part.description }}</i></small>
</td>
<td>
<button class='btn btn-default btn-create' onClick='newSupplierPartFromOrderWizard()' id='new_supplier_part_{{ part.id }}' part='{{ part.pk }}' title='{% trans "Create new supplier part" $}' type='button'>
<button class='btn btn-default btn-create' onClick='newSupplierPartFromOrderWizard()' id='new_supplier_part_{{ part.id }}' part='{{ part.pk }}' title='{% trans "Create new supplier part" %}' type='button'>
<span part='{{ part.pk }}' class='fas fa-plus-circle'></span>
</button>
</td>
@ -66,7 +66,7 @@
</div>
</td>
<td>
<button class='btn btn-default btn-remove' onclick='removeOrderRowFromOrderWizard()' id='del_item_{{ part.id }}' title='Remove part' type='button'>
<button class='btn btn-default btn-remove' onclick='removeOrderRowFromOrderWizard()' id='del_item_{{ part.id }}' title='{% trans "Remove part" %}' type='button'>
<span row='part_row_{{ part.id }}' class='fas fa-trash-alt icon-red'></span>
</button>
</td>

View File

@ -42,7 +42,7 @@
<button
class='btn btn-default btn-create'
id='new_po_{{ supplier.id }}'
title='Create new purchase order for {{ supplier.name }}'
title='{% trans "Create new purchase order for {{ supplier.name }}" %}'
type='button'
supplierid='{{ supplier.id }}'
onclick='newPurchaseOrderFromOrderWizard()'>

View File

@ -116,6 +116,7 @@ InvenTree | {% trans "Purchase Orders" %}
initialView: 'dayGridMonth',
nowIndicator: true,
aspectRatio: 2.5,
locale: '{{request.LANGUAGE_CODE}}',
datesSet: function() {
loadOrderEvents(calendar);
}

View File

@ -54,7 +54,7 @@
</div>
</td>
<td>
<button class='btn btn-default btn-remove' onClick="removeOrderRowFromOrderWizard()" id='del_item_{{ line.id }}' title='Remove line' type='button'>
<button class='btn btn-default btn-remove' onClick="removeOrderRowFromOrderWizard()" id='del_item_{{ line.id }}' title='{% trans "Remove line" %}' type='button'>
<span row='line_row_{{ line.id }}' class='fas fa-times-circle icon-red'></span>
</button>
</td>

View File

@ -49,7 +49,7 @@ src="{% static 'img/blank_image.png' %}"
<span class='fas fa-print'></span>
</button>
{% if roles.sales_order.change %}
<button type='button' class='btn btn-default' id='edit-order' title='Edit order information'>
<button type='button' class='btn btn-default' id='edit-order' title='{% trans "Edit order information" %}'>
<span class='fas fa-edit icon-green'></span>
</button>
{% if order.status == SalesOrderStatus.PENDING %}

View File

@ -67,7 +67,7 @@ function showAllocationSubTable(index, row, element) {
{
width: '50%',
field: 'allocated',
title: 'Quantity',
title: '{% trans "Quantity" %}',
formatter: function(value, row, index, field) {
var text = '';
@ -89,7 +89,7 @@ function showAllocationSubTable(index, row, element) {
},
{
field: 'buttons',
title: 'Actions',
title: '{% trans "Actions" %}',
formatter: function(value, row, index, field) {
var html = "<div class='btn-group float-right' role='group'>";
@ -167,7 +167,7 @@ function showFulfilledSubTable(index, row, element) {
}
$("#so-lines-table").inventreeTable({
formatNoMatches: function() { return "No matching line items"; },
formatNoMatches: function() { return "{% trans 'No matching line items' %}"; },
queryParams: {
order: {{ order.id }},
part_detail: true,
@ -196,7 +196,7 @@ $("#so-lines-table").inventreeTable({
columns: [
{
field: 'pk',
title: 'ID',
title: '{% trans "ID" %}',
visible: false,
switchable: false,
},
@ -204,7 +204,7 @@ $("#so-lines-table").inventreeTable({
sortable: true,
sortName: 'part__name',
field: 'part',
title: 'Part',
title: '{% trans "Part" %}',
formatter: function(value, row, index, field) {
if (row.part) {
return imageHoverIcon(row.part_detail.thumbnail) + renderLink(row.part_detail.full_name, `/part/${value}/`);
@ -216,12 +216,12 @@ $("#so-lines-table").inventreeTable({
{
sortable: true,
field: 'reference',
title: 'Reference'
title: '{% trans "Reference" %}'
},
{
sortable: true,
field: 'quantity',
title: 'Quantity',
title: '{% trans "Quantity" %}',
},
{
field: 'allocated',
@ -261,7 +261,7 @@ $("#so-lines-table").inventreeTable({
},
{
field: 'notes',
title: 'Notes',
title: '{% trans "Notes" %}',
},
{% if order.status == SalesOrderStatus.PENDING %}
{

View File

@ -115,6 +115,7 @@ InvenTree | {% trans "Sales Orders" %}
initialView: 'dayGridMonth',
nowIndicator: true,
aspectRatio: 2.5,
locale: '{{request.LANGUAGE_CODE}}',
datesSet: function() {
loadOrderEvents(calendar);
},

View File

@ -9,7 +9,7 @@ from django.db import transaction
from django.shortcuts import get_object_or_404
from django.core.exceptions import ValidationError
from django.urls import reverse
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from django.views.generic import DetailView, ListView, UpdateView
from django.views.generic.edit import FormMixin
from django.forms import HiddenInput
@ -1057,7 +1057,7 @@ class OrderParts(AjaxView):
data = {
'form_valid': valid,
'success': 'Ordered {n} parts'.format(n=len(self.parts))
'success': _('Ordered {n} parts').format(n=len(self.parts))
}
return self.renderJsonResponse(self.request, data=data)
@ -1348,7 +1348,7 @@ class SalesOrderAssignSerials(AjaxView, FormMixin):
'form_valid': valid,
'form_errors': self.form.errors.as_json(),
'non_field_errors': self.form.non_field_errors().as_json(),
'success': _("Allocated") + f" {len(self.stock_items)} " + _("items")
'success': _("Allocated {n} items").format(n=len(self.stock_items))
}
return self.renderJsonResponse(request, self.form, data)

View File

@ -275,7 +275,7 @@ class BomUploadManager:
elif ext in ['.xls', '.xlsx']:
raw_data = bom_file.read()
else:
raise ValidationError({'bom_file': _('Unsupported file format: {f}'.format(f=ext))})
raise ValidationError({'bom_file': _('Unsupported file format: {f}').format(f=ext)})
try:
self.data = tablib.Dataset().load(raw_data)

View File

@ -11,7 +11,7 @@ from InvenTree.fields import RoundingDecimalFormField
from mptt.fields import TreeNodeChoiceField
from django import forms
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
import common.models
@ -129,6 +129,7 @@ class BomDuplicateForm(HelperForm):
confirm = forms.BooleanField(
required=False, initial=False,
label=_('Confirm'),
help_text=_('Confirm BOM duplication')
)
@ -147,7 +148,7 @@ class BomValidateForm(HelperForm):
to confirm that the BOM for this part is valid
"""
validate = forms.BooleanField(required=False, initial=False, help_text=_('Confirm that the BOM is correct'))
validate = forms.BooleanField(required=False, initial=False, label=_('validate'), help_text=_('Confirm that the BOM is correct'))
class Meta:
model = Part
@ -159,7 +160,7 @@ class BomValidateForm(HelperForm):
class BomUploadSelectFile(HelperForm):
""" Form for importing a BOM. Provides a file input box for upload """
bom_file = forms.FileField(label='BOM file', required=True, help_text=_("Select BOM file to upload"))
bom_file = forms.FileField(label=_('BOM file'), required=True, help_text=_("Select BOM file to upload"))
class Meta:
model = Part
@ -336,9 +337,9 @@ class EditCategoryParameterTemplateForm(HelperForm):
class EditBomItemForm(HelperForm):
""" Form for editing a BomItem object """
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5)
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity'))
sub_part = PartModelChoiceField(queryset=Part.objects.all())
sub_part = PartModelChoiceField(queryset=Part.objects.all(), label=_('Sub part'))
class Meta:
model = BomItem
@ -365,6 +366,7 @@ class PartPriceForm(forms.Form):
quantity = forms.IntegerField(
required=True,
initial=1,
label=_('Quantity'),
help_text=_('Input quantity for price calculation')
)
@ -380,7 +382,7 @@ class EditPartSalePriceBreakForm(HelperForm):
Form for creating / editing a sale price for a part
"""
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5)
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity'))
class Meta:
model = PartSellPriceBreak

View File

@ -0,0 +1,218 @@
# Generated by Django 3.0.7 on 2021-04-04 20:16
import InvenTree.models
import InvenTree.validators
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import mptt.fields
import part.models
import stdimage.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('stock', '0058_stockitem_packaging'),
('part', '0063_bomitem_inherited'),
]
operations = [
migrations.AlterField(
model_name='bomitem',
name='checksum',
field=models.CharField(blank=True, help_text='BOM line checksum', max_length=128, verbose_name='Checksum'),
),
migrations.AlterField(
model_name='bomitem',
name='note',
field=models.CharField(blank=True, help_text='BOM item notes', max_length=500, verbose_name='Note'),
),
migrations.AlterField(
model_name='bomitem',
name='optional',
field=models.BooleanField(default=False, help_text='This BOM item is optional', verbose_name='Optional'),
),
migrations.AlterField(
model_name='bomitem',
name='overage',
field=models.CharField(blank=True, help_text='Estimated build wastage quantity (absolute or percentage)', max_length=24, validators=[InvenTree.validators.validate_overage], verbose_name='Overage'),
),
migrations.AlterField(
model_name='bomitem',
name='part',
field=models.ForeignKey(help_text='Select parent part', limit_choices_to={'assembly': True}, on_delete=django.db.models.deletion.CASCADE, related_name='bom_items', to='part.Part', verbose_name='Part'),
),
migrations.AlterField(
model_name='bomitem',
name='quantity',
field=models.DecimalField(decimal_places=5, default=1.0, help_text='BOM quantity for this BOM item', max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity'),
),
migrations.AlterField(
model_name='bomitem',
name='reference',
field=models.CharField(blank=True, help_text='BOM item reference', max_length=500, verbose_name='Reference'),
),
migrations.AlterField(
model_name='bomitem',
name='sub_part',
field=models.ForeignKey(help_text='Select part to be used in BOM', limit_choices_to={'component': True}, on_delete=django.db.models.deletion.CASCADE, related_name='used_in', to='part.Part', verbose_name='Sub part'),
),
migrations.AlterField(
model_name='part',
name='bom_checked_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='boms_checked', to=settings.AUTH_USER_MODEL, verbose_name='BOM checked by'),
),
migrations.AlterField(
model_name='part',
name='bom_checked_date',
field=models.DateField(blank=True, null=True, verbose_name='BOM checked date'),
),
migrations.AlterField(
model_name='part',
name='bom_checksum',
field=models.CharField(blank=True, help_text='Stored BOM checksum', max_length=128, verbose_name='BOM checksum'),
),
migrations.AlterField(
model_name='part',
name='creation_date',
field=models.DateField(auto_now_add=True, null=True, verbose_name='Creation Date'),
),
migrations.AlterField(
model_name='part',
name='creation_user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parts_created', to=settings.AUTH_USER_MODEL, verbose_name='Creation User'),
),
migrations.AlterField(
model_name='part',
name='image',
field=stdimage.models.StdImageField(blank=True, null=True, upload_to=part.models.rename_part_image, verbose_name='Image'),
),
migrations.AlterField(
model_name='part',
name='responsible',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parts_responible', to=settings.AUTH_USER_MODEL, verbose_name='Responsible'),
),
migrations.AlterField(
model_name='partattachment',
name='attachment',
field=models.FileField(help_text='Select file to attach', upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'),
),
migrations.AlterField(
model_name='partattachment',
name='comment',
field=models.CharField(blank=True, help_text='File comment', max_length=100, verbose_name='Comment'),
),
migrations.AlterField(
model_name='partattachment',
name='part',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='part.Part', verbose_name='Part'),
),
migrations.AlterField(
model_name='partattachment',
name='upload_date',
field=models.DateField(auto_now_add=True, null=True, verbose_name='upload date'),
),
migrations.AlterField(
model_name='partattachment',
name='user',
field=models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
migrations.AlterField(
model_name='partcategory',
name='default_keywords',
field=models.CharField(blank=True, help_text='Default keywords for parts in this category', max_length=250, null=True, verbose_name='Default keywords'),
),
migrations.AlterField(
model_name='partcategory',
name='default_location',
field=mptt.fields.TreeForeignKey(blank=True, help_text='Default location for parts in this category', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_categories', to='stock.StockLocation', verbose_name='Default Location'),
),
migrations.AlterField(
model_name='partcategory',
name='description',
field=models.CharField(blank=True, help_text='Description (optional)', max_length=250, verbose_name='Description'),
),
migrations.AlterField(
model_name='partcategory',
name='name',
field=models.CharField(help_text='Name', max_length=100, validators=[InvenTree.validators.validate_tree_name], verbose_name='Name'),
),
migrations.AlterField(
model_name='partcategory',
name='parent',
field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='part.PartCategory', verbose_name='parent'),
),
migrations.AlterField(
model_name='partcategoryparametertemplate',
name='category',
field=models.ForeignKey(help_text='Part Category', on_delete=django.db.models.deletion.CASCADE, related_name='parameter_templates', to='part.PartCategory', verbose_name='Category'),
),
migrations.AlterField(
model_name='partcategoryparametertemplate',
name='default_value',
field=models.CharField(blank=True, help_text='Default Parameter Value', max_length=500, verbose_name='Default Value'),
),
migrations.AlterField(
model_name='partcategoryparametertemplate',
name='parameter_template',
field=models.ForeignKey(help_text='Parameter Template', on_delete=django.db.models.deletion.CASCADE, related_name='part_categories', to='part.PartParameterTemplate', verbose_name='Parameter Template'),
),
migrations.AlterField(
model_name='partparameter',
name='data',
field=models.CharField(help_text='Parameter Value', max_length=500, verbose_name='Data'),
),
migrations.AlterField(
model_name='partparameter',
name='part',
field=models.ForeignKey(help_text='Parent Part', on_delete=django.db.models.deletion.CASCADE, related_name='parameters', to='part.Part', verbose_name='Part'),
),
migrations.AlterField(
model_name='partparameter',
name='template',
field=models.ForeignKey(help_text='Parameter Template', on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='part.PartParameterTemplate', verbose_name='Template'),
),
migrations.AlterField(
model_name='partparametertemplate',
name='name',
field=models.CharField(help_text='Parameter Name', max_length=100, unique=True, verbose_name='Name'),
),
migrations.AlterField(
model_name='partparametertemplate',
name='units',
field=models.CharField(blank=True, help_text='Parameter Units', max_length=25, verbose_name='Units'),
),
migrations.AlterField(
model_name='partrelated',
name='part_1',
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='related_parts_1', to='part.Part', verbose_name='Part 1'),
),
migrations.AlterField(
model_name='partrelated',
name='part_2',
field=models.ForeignKey(help_text='Select Related Part', on_delete=django.db.models.deletion.DO_NOTHING, related_name='related_parts_2', to='part.Part', verbose_name='Part 2'),
),
migrations.AlterField(
model_name='partsellpricebreak',
name='part',
field=models.ForeignKey(limit_choices_to={'salable': True}, on_delete=django.db.models.deletion.CASCADE, related_name='salepricebreaks', to='part.Part', verbose_name='Part'),
),
migrations.AlterField(
model_name='partstar',
name='part',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='starred_users', to='part.Part', verbose_name='Part'),
),
migrations.AlterField(
model_name='partstar',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='starred_parts', to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
migrations.AlterField(
model_name='parttesttemplate',
name='part',
field=models.ForeignKey(limit_choices_to={'trackable': True}, on_delete=django.db.models.deletion.CASCADE, related_name='test_templates', to='part.Part', verbose_name='Part'),
),
]

View File

@ -69,10 +69,11 @@ class PartCategory(InvenTreeTree):
'stock.StockLocation', related_name="default_categories",
null=True, blank=True,
on_delete=models.SET_NULL,
verbose_name=_('Default Location'),
help_text=_('Default location for parts in this category')
)
default_keywords = models.CharField(null=True, blank=True, max_length=250, help_text=_('Default keywords for parts in this category'))
default_keywords = models.CharField(null=True, blank=True, max_length=250, verbose_name=_('Default keywords'), help_text=_('Default keywords for parts in this category'))
def get_absolute_url(self):
return reverse('category-detail', kwargs={'pk': self.id})
@ -442,10 +443,10 @@ class Part(MPTTModel):
return
if self.pk == parent.pk:
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)".format(
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)").format(
p1=str(self),
p2=str(parent)
))})
)})
bom_items = self.get_bom_items()
@ -454,10 +455,10 @@ class Part(MPTTModel):
# Check for simple match
if item.sub_part == parent:
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)".format(
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)").format(
p1=str(parent),
p2=str(self)
))})
)})
# And recursively check too
item.sub_part.checkAddToBOM(parent)
@ -749,6 +750,7 @@ class Part(MPTTModel):
blank=True,
variations={'thumbnail': (128, 128)},
delete_orphans=False,
verbose_name=_('Image'),
)
default_location = TreeForeignKey(
@ -870,18 +872,18 @@ class Part(MPTTModel):
help_text=_('Part notes - supports Markdown formatting')
)
bom_checksum = models.CharField(max_length=128, blank=True, help_text=_('Stored BOM checksum'))
bom_checksum = models.CharField(max_length=128, blank=True, verbose_name=_('BOM checksum'), help_text=_('Stored BOM checksum'))
bom_checked_by = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True,
related_name='boms_checked')
verbose_name=_('BOM checked by'), related_name='boms_checked')
bom_checked_date = models.DateField(blank=True, null=True)
bom_checked_date = models.DateField(blank=True, null=True, verbose_name=_('BOM checked date'))
creation_date = models.DateField(auto_now_add=True, editable=False, blank=True, null=True)
creation_date = models.DateField(auto_now_add=True, editable=False, blank=True, null=True, verbose_name=_('Creation Date'))
creation_user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, related_name='parts_created')
creation_user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_('Creation User'), related_name='parts_created')
responsible = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, related_name='parts_responible')
responsible = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_('Responsible'), related_name='parts_responible')
def format_barcode(self, **kwargs):
""" Return a JSON string for formatting a barcode for this Part object """
@ -1851,7 +1853,7 @@ class PartAttachment(InvenTreeAttachment):
return os.path.join("part_files", str(self.part.id))
part = models.ForeignKey(Part, on_delete=models.CASCADE,
related_name='attachments')
verbose_name=_('Part'), related_name='attachments')
class PartSellPriceBreak(common.models.PriceBreak):
@ -1862,7 +1864,8 @@ class PartSellPriceBreak(common.models.PriceBreak):
part = models.ForeignKey(
Part, on_delete=models.CASCADE,
related_name='salepricebreaks',
limit_choices_to={'salable': True}
limit_choices_to={'salable': True},
verbose_name=_('Part')
)
class Meta:
@ -1880,9 +1883,9 @@ class PartStar(models.Model):
user: Link to a User object
"""
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='starred_users')
part = models.ForeignKey(Part, on_delete=models.CASCADE, verbose_name=_('Part'), related_name='starred_users')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='starred_parts')
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('User'), related_name='starred_parts')
class Meta:
unique_together = ['part', 'user']
@ -1955,6 +1958,7 @@ class PartTestTemplate(models.Model):
on_delete=models.CASCADE,
related_name='test_templates',
limit_choices_to={'trackable': True},
verbose_name=_('Part'),
)
test_name = models.CharField(
@ -2022,9 +2026,9 @@ class PartParameterTemplate(models.Model):
except PartParameterTemplate.DoesNotExist:
pass
name = models.CharField(max_length=100, help_text=_('Parameter Name'), unique=True)
name = models.CharField(max_length=100, verbose_name=_('Name'), help_text=_('Parameter Name'), unique=True)
units = models.CharField(max_length=25, help_text=_('Parameter Units'), blank=True)
units = models.CharField(max_length=25, verbose_name=_('Units'), help_text=_('Parameter Units'), blank=True)
class PartParameter(models.Model):
@ -2050,11 +2054,11 @@ class PartParameter(models.Model):
# Prevent multiple instances of a parameter for a single part
unique_together = ('part', 'template')
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='parameters', help_text=_('Parent Part'))
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='parameters', verbose_name=_('Part'), help_text=_('Parent Part'))
template = models.ForeignKey(PartParameterTemplate, on_delete=models.CASCADE, related_name='instances', help_text=_('Parameter Template'))
template = models.ForeignKey(PartParameterTemplate, on_delete=models.CASCADE, related_name='instances', verbose_name=_('Template'), help_text=_('Parameter Template'))
data = models.CharField(max_length=500, help_text=_('Parameter Value'))
data = models.CharField(max_length=500, verbose_name=_('Data'), help_text=_('Parameter Value'))
@classmethod
def create(cls, part, template, data, save=False):
@ -2095,15 +2099,18 @@ class PartCategoryParameterTemplate(models.Model):
category = models.ForeignKey(PartCategory,
on_delete=models.CASCADE,
related_name='parameter_templates',
verbose_name=_('Category'),
help_text=_('Part Category'))
parameter_template = models.ForeignKey(PartParameterTemplate,
on_delete=models.CASCADE,
related_name='part_categories',
verbose_name=_('Parameter Template'),
help_text=_('Parameter Template'))
default_value = models.CharField(max_length=500,
blank=True,
verbose_name=_('Default Value'),
help_text=_('Default Parameter Value'))
@ -2132,6 +2139,7 @@ class BomItem(models.Model):
# A link to the parent part
# Each part will get a reverse lookup field 'bom_items'
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='bom_items',
verbose_name=_('Part'),
help_text=_('Select parent part'),
limit_choices_to={
'assembly': True,
@ -2140,26 +2148,28 @@ class BomItem(models.Model):
# A link to the child item (sub-part)
# Each part will get a reverse lookup field 'used_in'
sub_part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='used_in',
verbose_name=_('Sub part'),
help_text=_('Select part to be used in BOM'),
limit_choices_to={
'component': True,
})
# Quantity required
quantity = models.DecimalField(default=1.0, max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], help_text=_('BOM quantity for this BOM item'))
quantity = models.DecimalField(default=1.0, max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], verbose_name=_('Quantity'), help_text=_('BOM quantity for this BOM item'))
optional = models.BooleanField(default=False, help_text=_("This BOM item is optional"))
optional = models.BooleanField(default=False, verbose_name=_('Optional'), help_text=_("This BOM item is optional"))
overage = models.CharField(max_length=24, blank=True, validators=[validators.validate_overage],
verbose_name=_('Overage'),
help_text=_('Estimated build wastage quantity (absolute or percentage)')
)
reference = models.CharField(max_length=500, blank=True, help_text=_('BOM item reference'))
reference = models.CharField(max_length=500, blank=True, verbose_name=_('Reference'), help_text=_('BOM item reference'))
# Note attached to this BOM line item
note = models.CharField(max_length=500, blank=True, help_text=_('BOM item notes'))
note = models.CharField(max_length=500, blank=True, verbose_name=_('Note'), help_text=_('BOM item notes'))
checksum = models.CharField(max_length=128, blank=True, help_text=_('BOM line checksum'))
checksum = models.CharField(max_length=128, blank=True, verbose_name=_('Checksum'), help_text=_('BOM line checksum'))
inherited = models.BooleanField(
default=False,
@ -2371,11 +2381,11 @@ class PartRelated(models.Model):
""" Store and handle related parts (eg. mating connector, crimps, etc.) """
part_1 = models.ForeignKey(Part, related_name='related_parts_1',
on_delete=models.DO_NOTHING)
verbose_name=_('Part 1'), on_delete=models.DO_NOTHING)
part_2 = models.ForeignKey(Part, related_name='related_parts_2',
on_delete=models.DO_NOTHING,
help_text=_('Select Related Part'))
verbose_name=_('Part 2'), help_text=_('Select Related Part'))
def __str__(self):
return f'{self.part_1} <--> {self.part_2}'

View File

@ -16,13 +16,13 @@
<div class='alert alert-block alert-info'>
{% else %}
<div class='alert alert-block alert-danger'>
The BOM for <i>{{ part.full_name }}</i> has changed, and must be validated.<br>
{% blocktrans with part=part.full_name %}The BOM for <i>{{ part }}</i> has changed, and must be validated.<br>{% endblocktrans %}
{% endif %}
The BOM for <i>{{ part.full_name }}</i> was last checked by {{ part.bom_checked_by }} on {{ part.bom_checked_date }}
{% blocktrans with part=part.full_name checker=part.bom_checked_by check_date=part.bom_checked_date %}The BOM for <i>{{ part }}</i> was last checked by {{ checker }} on {{ check_date }}{% endblocktrans %}
</div>
{% else %}
<div class='alert alert-danger alert-block'>
<b>The BOM for <i>{{ part.full_name }}</i> has not been validated.</b>
<b>{% blocktrans with part=part.full_name %}The BOM for <i>{{ part }}</i> has not been validated.{% endblocktrans %}</b>
</div>
{% endif %}

View File

@ -44,7 +44,7 @@
<div>
<input type='hidden' name='col_name_{{ forloop.counter0 }}' value='{{ col.name }}'/>
{{ col.name }}
<button class='btn btn-default btn-remove' onClick='removeColFromBomWizard()' id='del_col_{{ forloop.counter0 }}' style='display: inline; float: right;' title='Remove column'>
<button class='btn btn-default btn-remove' onClick='removeColFromBomWizard()' id='del_col_{{ forloop.counter0 }}' style='display: inline; float: right;' title='{% trans "Remove column" %}'>
<span col_id='{{ forloop.counter0 }}' class='fas fa-trash-alt icon-red'></span>
</button>
</div>
@ -73,7 +73,7 @@
{% for row in bom_rows %}
<tr>
<td>
<button class='btn btn-default btn-remove' onClick='removeRowFromBomWizard()' id='del_row_{{ forloop.counter }}' style='display: inline; float: right;' title='Remove row'>
<button class='btn btn-default btn-remove' onClick='removeRowFromBomWizard()' id='del_row_{{ forloop.counter }}' style='display: inline; float: right;' title='{% trans "Remove row" %}'>
<span row_id='{{ forloop.counter }}' class='fas fa-trash-alt icon-red'></span>
</button>
</td>

View File

@ -24,7 +24,7 @@
</div>
<form method="post" action='' class='js-modal-form' enctype="multipart/form-data">
<button type="submit" class="save btn btn-default">Upload File</button>
<button type="submit" class="save btn btn-default">{% trans 'Upload File' %}</button>
{% csrf_token %}
{% load crispy_forms_tags %}

View File

@ -1,10 +1,12 @@
{% extends "modal_form.html" %}
{% load i18n %}
{% block pre_form_content %}
Confirm that the Bill of Materials (BOM) is valid for:<br><i>{{ part.full_name }}</i>
{% blocktrans with part.full_name as part %}Confirm that the Bill of Materials (BOM) is valid for:<br><i>{{ part }}</i>{% endblocktrans %}
<div class='alert alert-warning alert-block'>
This will validate each line in the BOM.
{% trans 'This will validate each line in the BOM.' %}
</div>
{% endblock %}

View File

@ -65,8 +65,8 @@
reload: true,
secondary: [{
field: 'template',
label: 'New Template',
title: 'Create New Parameter Template',
label: '{% trans "New Template" %}',
title: '{% trans "Create New Parameter Template" %}',
url: "{% url 'part-param-template-create' %}"
}],
});

View File

@ -246,7 +246,7 @@
launchModalForm(
"{% url 'part-pricing' part.id %}",
{
submit_text: 'Calculate',
submit_text: '{% trans "Calculate" %}',
hideErrorMessage: true,
}
);

View File

@ -1,35 +1,37 @@
{% extends "modal_form.html" %}
{% load i18n %}
{% block pre_form_content %}
<div class='alert alert-info alert-block'>
Pricing information for:<br>
{% trans 'Pricing information for:' %}<br>
{{ part }}.
</div>
<h4>Quantity</h4>
<h4>{% trans 'Quantity' %}</h4>
<table class='table table-striped table-condensed'>
<tr>
<td><b>Part</b></td>
<td><b>{% trans 'Part' %}</b></td>
<td colspan='2'>{{ part }}</td>
</tr>
<tr>
<td><b>Quantity</b></td>
<td><b>{% trans 'Quantity' %}</b></td>
<td colspan='2'>{{ quantity }}</td>
</tr>
</table>
{% if part.supplier_count > 0 %}
<h4>Supplier Pricing</h4>
<h4>{% trans 'Supplier Pricing' %}</h4>
<table class='table table-striped table-condensed'>
{% if min_total_buy_price %}
<tr>
<td><b>Unit Cost</b></td>
<td><b>{% trans 'Unit Cost' %}</b></td>
<td>Min: {% include "price.html" with price=min_unit_buy_price %}</td>
<td>Max: {% include "price.html" with price=max_unit_buy_price %}</td>
</tr>
{% if quantity > 1 %}
<tr>
<td><b>Total Cost</b></td>
<td><b>{% trans 'Total Cost' %}</b></td>
<td>Min: {% include "price.html" with price=min_total_buy_price %}</td>
<td>Max: {% include "price.html" with price=max_total_buy_price %}</td>
</tr>
@ -37,7 +39,7 @@ Pricing information for:<br>
{% else %}
<tr>
<td colspan='3'>
<span class='warning-msg'><i>No supplier pricing available</i></span>
<span class='warning-msg'><i>{% trans 'No supplier pricing available' %}</i></span>
</td>
</tr>
{% endif %}
@ -45,17 +47,17 @@ Pricing information for:<br>
{% endif %}
{% if part.bom_count > 0 %}
<h4>BOM Pricing</h4>
<h4>{% trans 'BOM Pricing' %}</h4>
<table class='table table-striped table-condensed'>
{% if min_total_bom_price %}
<tr>
<td><b>Unit Cost</b></td>
<td><b>{% trans 'Unit Cost' %}</b></td>
<td>Min: {% include "price.html" with price=min_unit_bom_price %}</td>
<td>Max: {% include "price.html" with price=max_unit_bom_price %}</td>
</tr>
{% if quantity > 1 %}
<tr>
<td><b>Total Cost</b></td>
<td><b>{% trans 'Total Cost' %}</b></td>
<td>Min: {% include "price.html" with price=min_total_bom_price %}</td>
<td>Max: {% include "price.html" with price=max_total_bom_price %}</td>
</tr>
@ -63,14 +65,14 @@ Pricing information for:<br>
{% if part.has_complete_bom_pricing == False %}
<tr>
<td colspan='3'>
<span class='warning-msg'><i>Note: BOM pricing is incomplete for this part</i></span>
<span class='warning-msg'><i>{% trans 'Note: BOM pricing is incomplete for this part' %}</i></span>
</td>
</tr>
{% endif %}
{% else %}
<tr>
<td colspan='3'>
<span class='warning-msg'><i>No BOM pricing available</i></span>
<span class='warning-msg'><i>{% trans 'No BOM pricing available' %}</i></span>
</td>
</tr>
{% endif %}
@ -80,7 +82,7 @@ Pricing information for:<br>
{% if min_unit_buy_price or min_unit_bom_price %}
{% else %}
<div class='alert alert-danger alert-block'>
No pricing information is available for this part.
{% trans 'No pricing information is available for this part.' %}
</div>
{% endif %}

View File

@ -342,7 +342,7 @@ class PartSetCategory(AjaxUpdateView):
data = {
'form_valid': valid,
'success': _('Set category for {n} parts'.format(n=len(self.parts)))
'success': _('Set category for {n} parts').format(n=len(self.parts))
}
if valid:

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from django.conf.urls import url, include
from django.core.exceptions import ValidationError, FieldError
from django.http import HttpResponse

View File

@ -0,0 +1,35 @@
# Generated by Django 3.0.7 on 2021-04-03 18:37
import django.core.validators
from django.db import migrations, models
import report.models
class Migration(migrations.Migration):
dependencies = [
('report', '0014_purchaseorderreport_salesorderreport'),
]
operations = [
migrations.AlterField(
model_name='reportasset',
name='asset',
field=models.FileField(help_text='Report asset file', upload_to=report.models.rename_asset, verbose_name='Asset'),
),
migrations.AlterField(
model_name='reportasset',
name='description',
field=models.CharField(help_text='Asset file description', max_length=250, verbose_name='Description'),
),
migrations.AlterField(
model_name='reportsnippet',
name='description',
field=models.CharField(help_text='Snippet file description', max_length=250, verbose_name='Description'),
),
migrations.AlterField(
model_name='reportsnippet',
name='snippet',
field=models.FileField(help_text='Report snippet file', upload_to=report.models.rename_snippet, validators=[django.core.validators.FileExtensionValidator(allowed_extensions=['html', 'htm'])], verbose_name='Snippet'),
),
]

View File

@ -497,11 +497,12 @@ class ReportSnippet(models.Model):
snippet = models.FileField(
upload_to=rename_snippet,
verbose_name=_('Snippet'),
help_text=_('Report snippet file'),
validators=[FileExtensionValidator(allowed_extensions=['html', 'htm'])],
)
description = models.CharField(max_length=250, help_text=_("Snippet file description"))
description = models.CharField(max_length=250, verbose_name=_('Description'), help_text=_("Snippet file description"))
def rename_asset(instance, filename):
@ -536,7 +537,8 @@ class ReportAsset(models.Model):
asset = models.FileField(
upload_to=rename_asset,
verbose_name=_('Asset'),
help_text=_("Report asset file"),
)
description = models.CharField(max_length=250, help_text=_("Asset file description"))
description = models.CharField(max_length=250, verbose_name=_('Description'), help_text=_("Asset file description"))

View File

@ -11,6 +11,7 @@ from django.conf.urls import url, include
from django.urls import reverse
from django.http import JsonResponse
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from .models import StockLocation, StockItem
from .models import StockItemTracking
@ -195,7 +196,7 @@ class StockCount(StockAdjust):
if item['item'].stocktake(item['quantity'], request.user, notes=self.notes):
n += 1
return Response({'success': 'Updated stock for {n} items'.format(n=n)})
return Response({'success': _('Updated stock for {n} items').format(n=n)})
class StockAdd(StockAdjust):
@ -264,7 +265,7 @@ class StockTransfer(StockAdjust):
if item['item'].move(location, self.notes, request.user, quantity=item['quantity']):
n += 1
return Response({'success': 'Moved {n} parts to {loc}'.format(
return Response({'success': _('Moved {n} parts to {loc}').format(
n=n,
loc=str(location),
)})

View File

@ -7,7 +7,7 @@ from __future__ import unicode_literals
from django import forms
from django.forms.utils import ErrorDict
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator
from django.core.exceptions import ValidationError
@ -111,10 +111,11 @@ class CreateStockItemForm(HelperForm):
""" Form for creating a new StockItem """
expiry_date = DatePickerFormField(
help_text=('Expiration date for this stock item'),
label=_('Expiry Date'),
help_text=_('Expiration date for this stock item'),
)
serial_numbers = forms.CharField(label=_('Serial numbers'), required=False, help_text=_('Enter unique serial numbers (or leave blank)'))
serial_numbers = forms.CharField(label=_('Serial Numbers'), required=False, help_text=_('Enter unique serial numbers (or leave blank)'))
def __init__(self, *args, **kwargs):
@ -165,13 +166,13 @@ class CreateStockItemForm(HelperForm):
class SerializeStockForm(HelperForm):
""" Form for serializing a StockItem. """
destination = TreeNodeChoiceField(queryset=StockLocation.objects.all(), label='Destination', required=True, help_text='Destination for serialized stock (by default, will remain in current location)')
destination = TreeNodeChoiceField(queryset=StockLocation.objects.all(), label=_('Destination'), required=True, help_text=_('Destination for serialized stock (by default, will remain in current location)'))
serial_numbers = forms.CharField(label='Serial numbers', required=True, help_text='Unique serial numbers (must match quantity)')
serial_numbers = forms.CharField(label=_('Serial numbers'), required=True, help_text=_('Unique serial numbers (must match quantity)'))
note = forms.CharField(label='Notes', required=False, help_text='Add transaction note (optional)')
note = forms.CharField(label=_('Notes'), required=False, help_text=_('Add transaction note (optional)'))
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5)
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity'))
def __init__(self, *args, **kwargs):
@ -263,7 +264,7 @@ class ExportOptionsForm(HelperForm):
file_format = forms.ChoiceField(label=_('File Format'), help_text=_('Select output file format'))
include_sublocations = forms.BooleanField(required=False, initial=True, help_text=_("Include stock items in sub locations"))
include_sublocations = forms.BooleanField(required=False, initial=True, label=_('Include sublocations'), help_text=_("Include stock items in sub locations"))
class Meta:
model = StockLocation
@ -402,7 +403,8 @@ class EditStockItemForm(HelperForm):
"""
expiry_date = DatePickerFormField(
help_text=('Expiration date for this stock item'),
label=_('Expiry Date'),
help_text=_('Expiration date for this stock item'),
)
class Meta:

View File

@ -0,0 +1,92 @@
# Generated by Django 3.0.7 on 2021-04-04 20:16
import InvenTree.fields
import InvenTree.models
import InvenTree.validators
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import mptt.fields
class Migration(migrations.Migration):
dependencies = [
('users', '0005_owner_model'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('stock', '0058_stockitem_packaging'),
]
operations = [
migrations.AlterField(
model_name='stockitem',
name='delete_on_deplete',
field=models.BooleanField(default=True, help_text='Delete this Stock Item when stock is depleted', verbose_name='Delete on deplete'),
),
migrations.AlterField(
model_name='stockitem',
name='owner',
field=models.ForeignKey(blank=True, help_text='Select Owner', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stock_items', to='users.Owner', verbose_name='Owner'),
),
migrations.AlterField(
model_name='stockitemattachment',
name='attachment',
field=models.FileField(help_text='Select file to attach', upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'),
),
migrations.AlterField(
model_name='stockitemattachment',
name='comment',
field=models.CharField(blank=True, help_text='File comment', max_length=100, verbose_name='Comment'),
),
migrations.AlterField(
model_name='stockitemattachment',
name='upload_date',
field=models.DateField(auto_now_add=True, null=True, verbose_name='upload date'),
),
migrations.AlterField(
model_name='stockitemattachment',
name='user',
field=models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
migrations.AlterField(
model_name='stockitemtracking',
name='link',
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external page for further information', verbose_name='Link'),
),
migrations.AlterField(
model_name='stockitemtracking',
name='notes',
field=models.CharField(blank=True, help_text='Entry notes', max_length=512, verbose_name='Notes'),
),
migrations.AlterField(
model_name='stockitemtracking',
name='quantity',
field=models.DecimalField(decimal_places=5, default=1, max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity'),
),
migrations.AlterField(
model_name='stockitemtracking',
name='title',
field=models.CharField(help_text='Tracking entry title', max_length=250, verbose_name='Title'),
),
migrations.AlterField(
model_name='stocklocation',
name='description',
field=models.CharField(blank=True, help_text='Description (optional)', max_length=250, verbose_name='Description'),
),
migrations.AlterField(
model_name='stocklocation',
name='name',
field=models.CharField(help_text='Name', max_length=100, validators=[InvenTree.validators.validate_tree_name], verbose_name='Name'),
),
migrations.AlterField(
model_name='stocklocation',
name='owner',
field=models.ForeignKey(blank=True, help_text='Select Owner', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stock_locations', to='users.Owner', verbose_name='Owner'),
),
migrations.AlterField(
model_name='stocklocation',
name='parent',
field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='stock.StockLocation', verbose_name='parent'),
),
]

View File

@ -51,7 +51,8 @@ class StockLocation(InvenTreeTree):
"""
owner = models.ForeignKey(Owner, on_delete=models.SET_NULL, blank=True, null=True,
help_text='Select Owner',
verbose_name=_('Owner'),
help_text=_('Select Owner'),
related_name='stock_locations')
def get_absolute_url(self):
@ -483,7 +484,7 @@ class StockItem(MPTTModel):
review_needed = models.BooleanField(default=False)
delete_on_deplete = models.BooleanField(default=True, help_text=_('Delete this Stock Item when stock is depleted'))
delete_on_deplete = models.BooleanField(default=True, verbose_name=_('Delete on deplete'), help_text=_('Delete this Stock Item when stock is depleted'))
status = models.PositiveIntegerField(
default=StockStatus.OK,
@ -507,7 +508,8 @@ class StockItem(MPTTModel):
)
owner = models.ForeignKey(Owner, on_delete=models.SET_NULL, blank=True, null=True,
help_text='Select Owner',
verbose_name=_('Owner'),
help_text=_('Select Owner'),
related_name='stock_items')
def is_stale(self):
@ -948,7 +950,7 @@ class StockItem(MPTTModel):
raise ValidationError({"quantity": _("Quantity must be greater than zero")})
if quantity > self.quantity:
raise ValidationError({"quantity": _("Quantity must not exceed available stock quantity ({n})".format(n=self.quantity))})
raise ValidationError({"quantity": _("Quantity must not exceed available stock quantity ({n})").format(n=self.quantity)})
if not type(serials) in [list, tuple]:
raise ValidationError({"serial_numbers": _("Serial numbers must be a list of integers")})
@ -989,7 +991,7 @@ class StockItem(MPTTModel):
new_item.addTransactionNote(_('Add serial number'), user, notes=notes)
# Remove the equivalent number of items
self.take_stock(quantity, user, notes=_('Serialized {n} items'.format(n=quantity)))
self.take_stock(quantity, user, notes=_('Serialized {n} items').format(n=quantity))
@transaction.atomic
def copyHistoryFrom(self, other):
@ -1548,17 +1550,17 @@ class StockItemTracking(models.Model):
date = models.DateTimeField(auto_now_add=True, editable=False)
title = models.CharField(blank=False, max_length=250, help_text=_('Tracking entry title'))
title = models.CharField(blank=False, max_length=250, verbose_name=_('Title'), help_text=_('Tracking entry title'))
notes = models.CharField(blank=True, max_length=512, help_text=_('Entry notes'))
notes = models.CharField(blank=True, max_length=512, verbose_name=_('Notes'), help_text=_('Entry notes'))
link = InvenTreeURLField(blank=True, help_text=_('Link to external page for further information'))
link = InvenTreeURLField(blank=True, verbose_name=_('Link'), help_text=_('Link to external page for further information'))
user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)
system = models.BooleanField(default=False)
quantity = models.DecimalField(max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], default=1)
quantity = models.DecimalField(max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], default=1, verbose_name=_('Quantity'))
# TODO
# image = models.ImageField(upload_to=func, max_length=255, null=True, blank=True)

View File

@ -451,7 +451,7 @@ $("#stock-edit").click(function () {
"{% url 'stock-item-edit' item.id %}",
{
reload: true,
submit_text: "Save",
submit_text: '{% trans "Save" %}',
}
);
});

View File

@ -160,7 +160,7 @@
$("#stock-export").click(function() {
launchModalForm("{% url 'stock-export-options' %}", {
submit_text: "Export",
submit_text: '{% trans "Export" %}',
success: function(response) {
var url = "{% url 'stock-export' %}";
@ -188,8 +188,8 @@
secondary: [
{
field: 'parent',
label: 'New Location',
title: 'Create new location',
label: '{% trans "New Location" %}',
title: '{% trans "Create new location" %}',
url: "{% url 'stock-location-create' %}",
},
]

View File

@ -40,7 +40,7 @@
<input type='hidden' name='stock-id-{{ item.id }}' value='{{ item.new_quantity }}'/>
{% endif %}
</td>
<td><button class='btn btn-default btn-remove' onclick='removeStockRow()' id='del-{{ item.id }}' title='Remove item' type='button'><span row='stock-row-{{ item.id }}' class='fas fa-trash-alt icon-red'></span></button></td>
<td><button class='btn btn-default btn-remove' onclick='removeStockRow()' id='del-{{ item.id }}' title='{% trans "Remove item" %}' type='button'><span row='stock-row-{{ item.id }}' class='fas fa-trash-alt icon-red'></span></button></td>
</tr>
{% endfor %}
</table>

View File

@ -14,7 +14,7 @@ from django.urls import reverse
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from moneyed import CURRENCIES
@ -965,7 +965,7 @@ class StockAdjust(AjaxView, FormMixin):
context['stock_action'] = self.stock_action.strip().lower()
context['stock_action_title'] = self.stock_action.capitalize()
context['stock_action_title'] = self.stock_action_title
# Quantity column will be read-only in some circumstances
context['edit_quantity'] = not self.stock_action == 'delete'
@ -993,17 +993,18 @@ class StockAdjust(AjaxView, FormMixin):
if self.stock_action not in ['move', 'count', 'take', 'add', 'delete']:
self.stock_action = 'count'
# Choose the form title based on the action
# Choose form title and action column based on the action
titles = {
'move': _('Move Stock Items'),
'count': _('Count Stock Items'),
'take': _('Remove From Stock'),
'add': _('Add Stock Items'),
'delete': _('Delete Stock Items')
'move': [_('Move Stock Items'), _('Move')],
'count': [_('Count Stock Items'), _('Count')],
'take': [_('Remove From Stock'), _('Take')],
'add': [_('Add Stock Items'), _('Add')],
'delete': [_('Delete Stock Items'), _('Delete')],
}
self.ajax_form_title = titles[self.stock_action]
self.ajax_form_title = titles[self.stock_action][0]
self.stock_action_title = titles[self.stock_action][1]
# Save list of items!
self.stock_items = self.get_GET_items()
@ -1039,7 +1040,7 @@ class StockAdjust(AjaxView, FormMixin):
if self.stock_action in ['move', 'take']:
if item.new_quantity > item.quantity:
item.error = _('Quantity must not exceed {x}'.format(x=item.quantity))
item.error = _('Quantity must not exceed {x}').format(x=item.quantity)
valid = False
continue
@ -1118,7 +1119,7 @@ class StockAdjust(AjaxView, FormMixin):
count += 1
return f"{_('Added stock to ')} {count} {_('items')}"
return _('Added stock to {n} items').format(n=count)
def do_take(self):
@ -1133,7 +1134,7 @@ class StockAdjust(AjaxView, FormMixin):
count += 1
return f"{_('Removed stock from ')} {count} {_('items')}"
return _('Removed stock from {n} items').format(n=count)
def do_count(self):
@ -1189,9 +1190,9 @@ class StockAdjust(AjaxView, FormMixin):
return _('No items were moved')
else:
return _('Moved {n} items to {dest}'.format(
return _('Moved {n} items to {dest}').format(
n=count,
dest=destination.pathstring))
dest=destination.pathstring)
def do_delete(self):
""" Delete multiple stock items """
@ -1208,7 +1209,7 @@ class StockAdjust(AjaxView, FormMixin):
count += 1
return _("Deleted {n} stock items".format(n=count))
return _("Deleted {n} stock items").format(n=count)
class StockItemEdit(AjaxUpdateView):

View File

@ -133,14 +133,14 @@ InvenTree | {% trans "Search Results" %}
columns: [
{
field: 'name',
title: 'Name',
title: '{% trans "Name" %}',
formatter: function(value, row, index, field) {
return renderLink(value, '/part/category/' + row.pk + '/');
},
},
{
field: 'description',
title: 'Description',
title: '{% trans "Description" %}',
},
],
});
@ -270,14 +270,14 @@ InvenTree | {% trans "Search Results" %}
columns: [
{
field: 'name',
title: 'Name',
title: '{% trans "Name" %}',
formatter: function(value, row, index, field) {
return renderLink(row.pathstring, '/stock/location/' + row.pk + '/');
},
},
{
field: 'description',
title: 'Description',
title: '{% trans "Description" %}',
},
],
});

View File

@ -8,7 +8,7 @@
<span class='fas {{ icon }}'></span>
{% endif %}
</td>
<td><b>{{ setting.name }}</b></td>
<td><b>{% trans setting.name %}</b></td>
<td>
{% if setting.is_bool %}
<div>
@ -24,7 +24,7 @@
{% endif %}
{% endif %}
<td>
{{ setting.description }}
{% trans setting.description %}
</td>
<td>
<div class='btn-group float-right'>

View File

@ -34,16 +34,20 @@
<td>{% trans "Django Version" %}</td>
<td><a href="https://www.djangoproject.com/">{% django_version %}</a></td>
</tr>
{% inventree_commit_hash as hash %}
{% if hash %}
<tr>
<td><span class='fas fa-code-branch'></span></td>
<td>{% trans "Commit Hash" %}</td>
<td><a href="https://github.com/inventree/InvenTree/commit/{% inventree_commit_hash %}">{% inventree_commit_hash %}</a></td>
<td>{% trans "Commit Hash" %}</td><td>{{ hash }}</td>
</tr>
{% endif %}
{% inventree_commit_date as commit_date %}
{% if commit_date %}
<tr>
<td><span class='fas fa-calendar-alt'></span></td>
<td>{% trans "Commit Date" %}</td>
<td>{% inventree_commit_date %}</td>
<td>{% trans "Commit Date" %}</td><td>{{ commit_date }}</td>
</tr>
{% endif %}
<tr>
<td><span class='fas fa-book'></span></td>
<td>{% trans "InvenTree Documentation" %}</td>
@ -74,4 +78,4 @@
</div>
</div>
</div>
</div>
</div>

View File

@ -134,12 +134,12 @@ InvenTree
<script type='text/javascript' src='{% static "bootstrap-table/extensions/treegrid/bootstrap-table-treegrid.js" %}'></script>
<script type="text/javascript" src="{% static 'fullcalendar/main.js' %}"></script>
<script type="text/javascript" src="{% static 'fullcalendar/locales-all.js' %}"></script>
<script type="text/javascript" src="{% static 'script/select2/select2.js' %}"></script>
<script type='text/javascript' src="{% static 'script/moment.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/filters.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/notification.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/sidenav.js' %}"></script>
@ -156,6 +156,7 @@ InvenTree
<script type='text/javascript' src="{% url 'calendar.js' %}"></script>
<script type='text/javascript' src="{% url 'tables.js' %}"></script>
<script type='text/javascript' src="{% url 'table_filters.js' %}"></script>
<script type='text/javascript' src="{% url 'filters.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/solid.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/brands.js' %}"></script>
@ -180,6 +181,7 @@ $(document).ready(function () {
});
{% endif %}
moment.locale('{{request.LANGUAGE_CODE}}');
});
</script>

View File

@ -1,3 +1,5 @@
{% load i18n %}
/**
* Code for managing query filters / table options.
*
@ -188,7 +190,7 @@ function generateAvailableFilterList(tableKey) {
var html = `<select class='form-control filter-input' id='${id}' name='tag'>`;
html += `<option value=''>Select filter</option>`;
html += "<option value=''>{% trans 'Select filter' %}</option>";
for (var opt in remaining) {
var title = getFilterTitle(tableKey, opt);
@ -263,10 +265,10 @@ function setupFilterList(tableKey, table, target) {
// One blank slate, please
element.empty();
element.append(`<button id='${add}' title='Add new filter' class='btn btn-default filter-tag'><span class='fas fa-filter'></span></button>`);
element.append(`<button id='${add}' title='{% trans "Add new filter" %}' class='btn btn-default filter-tag'><span class='fas fa-filter'></span></button>`);
if (Object.keys(filters).length > 0) {
element.append(`<button id='${clear}' title='Clear all filters' class='btn btn-default filter-tag'><span class='fas fa-trash-alt'></span></button>`);
element.append(`<button id='${clear}' title='{% trans "Clear all filters" %}' class='btn btn-default filter-tag'><span class='fas fa-trash-alt'></span></button>`);
}
for (var key in filters) {
@ -291,7 +293,7 @@ function setupFilterList(tableKey, table, target) {
html += generateAvailableFilterList(tableKey);
html += generateFilterInput(tableKey);
html += `<button title='Create filter' class='btn btn-default filter-tag' id='${make}'><span class='fas fa-plus'></span></button>`;
html += `<button title='{% trans "Create filter" %}' class='btn btn-default filter-tag' id='${make}'><span class='fas fa-plus'></span></button>`;
//html += '</div>';

View File

@ -253,7 +253,7 @@ function loadingMessageContent() {
*/
// TODO - This can be made a lot better
return "<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> Waiting for server...";
return "<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> {% trans 'Waiting for server...' %}";
}

View File

@ -976,8 +976,8 @@ function loadStockTrackingTable(table, options) {
formatter: function(value, row, index, field) {
// Manually created entries can be edited or deleted
if (!row.system) {
var bEdit = "<button title='Edit tracking entry' class='btn btn-entry-edit btn-default btn-glyph' type='button' url='/stock/track/" + row.pk + "/edit/'><span class='fas fa-edit'/></button>";
var bDel = "<button title='Delete tracking entry' class='btn btn-entry-delete btn-default btn-glyph' type='button' url='/stock/track/" + row.pk + "/delete/'><span class='fas fa-trash-alt icon-red'/></button>";
var bEdit = "<button title='{% trans 'Edit tracking entry' %}' class='btn btn-entry-edit btn-default btn-glyph' type='button' url='/stock/track/" + row.pk + "/edit/'><span class='fas fa-edit'/></button>";
var bDel = "<button title='{% trans 'Delete tracking entry' %}' class='btn btn-entry-delete btn-default btn-glyph' type='button' url='/stock/track/" + row.pk + "/delete/'><span class='fas fa-trash-alt icon-red'/></button>";
return "<div class='btn-group' role='group'>" + bEdit + bDel + "</div>";
} else {

View File

@ -1 +1 @@
<button type='button' class='btn btn-default' id='show-qr-code' title='Show QR code'><span class='fas fa-qrcode'></span></button>
<button type='button' class='btn btn-default' id='show-qr-code' title='{% trans "Show QR Code" %}'><span class='fas fa-qrcode'></span></button>