added more translation-strings

This commit is contained in:
Matthias 2021-04-04 22:44:14 +02:00
parent c68220a597
commit 20c455384e
39 changed files with 2886 additions and 1653 deletions

View File

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

View File

@ -123,6 +123,7 @@ class DeleteForm(forms.Form):
confirm_delete = forms.BooleanField( confirm_delete = forms.BooleanField(
required=False, required=False,
initial=False, initial=False,
label=_('Confirm delete'),
help_text=_('Confirm item deletion') help_text=_('Confirm item deletion')
) )
@ -155,6 +156,7 @@ class SetPasswordForm(HelperForm):
required=True, required=True,
initial='', initial='',
widget=forms.PasswordInput(attrs={'autocomplete': 'off'}), widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
label=_('Enter password'),
help_text=_('Enter new password')) help_text=_('Enter new password'))
confirm_password = forms.CharField(max_length=100, confirm_password = forms.CharField(max_length=100,
@ -162,6 +164,7 @@ class SetPasswordForm(HelperForm):
required=True, required=True,
initial='', initial='',
widget=forms.PasswordInput(attrs={'autocomplete': 'off'}), widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
label=_('Confirm password'),
help_text=_('Confirm new password')) help_text=_('Confirm new password'))
class Meta: class Meta:

View File

@ -382,17 +382,17 @@ def extract_serial_numbers(serials, expected_quantity):
if a < b: if a < b:
for n in range(a, b + 1): for n in range(a, b + 1):
if n in numbers: if n in numbers:
errors.append(_('Duplicate serial: {n}'.format(n=n))) errors.append(_('Duplicate serial: {n}').format(n=n))
else: else:
numbers.append(n) numbers.append(n)
else: else:
errors.append(_("Invalid group: {g}".format(g=group))) errors.append(_("Invalid group: {g}").format(g=group))
except ValueError: except ValueError:
errors.append(_("Invalid group: {g}".format(g=group))) errors.append(_("Invalid group: {g}").format(g=group))
continue continue
else: else:
errors.append(_("Invalid group: {g}".format(g=group))) errors.append(_("Invalid group: {g}").format(g=group))
continue continue
else: else:
@ -409,7 +409,7 @@ def extract_serial_numbers(serials, expected_quantity):
# The number of extracted serial numbers must match the expected quantity # The number of extracted serial numbers must match the expected quantity
if not expected_quantity == len(numbers): 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 return numbers

View File

@ -60,7 +60,7 @@ def validate_part_ipn(value):
match = re.search(pattern, value) match = re.search(pattern, value)
if match is None: 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): def validate_build_order_reference(value):

View File

@ -117,6 +117,7 @@ class BuildOutputDeleteForm(HelperForm):
confirm = forms.BooleanField( confirm = forms.BooleanField(
required=False, required=False,
label=_('Confirm'),
help_text=_('Confirm deletion of build output') help_text=_('Confirm deletion of build output')
) )
@ -138,7 +139,7 @@ class UnallocateBuildForm(HelperForm):
Form for auto-de-allocation of stock from a build 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( output_id = forms.IntegerField(
required=False, required=False,
@ -162,7 +163,7 @@ class UnallocateBuildForm(HelperForm):
class AutoAllocateForm(HelperForm): class AutoAllocateForm(HelperForm):
""" Form for auto-allocation of stock to a build """ """ 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 # Keep track of which build output we are interested in
output = forms.ModelChoiceField( output = forms.ModelChoiceField(
@ -209,15 +210,17 @@ class CompleteBuildOutputForm(HelperForm):
location = forms.ModelChoiceField( location = forms.ModelChoiceField(
queryset=StockLocation.objects.all(), queryset=StockLocation.objects.all(),
label = _('Location'),
help_text=_('Location of completed parts'), help_text=_('Location of completed parts'),
) )
confirm_incomplete = forms.BooleanField( confirm_incomplete = forms.BooleanField(
required=False, required=False,
label=_('Confirm incomplete'),
help_text=_("Confirm completion with incomplete stock allocation") 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( output = forms.ModelChoiceField(
queryset=StockItem.objects.all(), # Queryset is narrowed in the view queryset=StockItem.objects.all(), # Queryset is narrowed in the view
@ -237,7 +240,7 @@ class CompleteBuildOutputForm(HelperForm):
class CancelBuildForm(HelperForm): class CancelBuildForm(HelperForm):
""" Form for cancelling a build """ """ 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: class Meta:
model = Build model = Build
@ -251,7 +254,7 @@ class EditBuildItemForm(HelperForm):
Form for creating (or editing) a BuildItem object. 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()) part_id = forms.IntegerField(required=False, widget=forms.HiddenInput())

View File

@ -1,7 +1,8 @@
# Generated by Django 3.0.7 on 2021-04-03 12:10 # Generated by Django 3.0.7 on 2021-04-04 20:16
import InvenTree.models import InvenTree.models
from django.conf import settings from django.conf import settings
import django.core.validators
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@ -9,8 +10,9 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('stock', '0058_stockitem_packaging'),
('users', '0005_owner_model'), ('users', '0005_owner_model'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('build', '0026_auto_20210216_1539'), ('build', '0026_auto_20210216_1539'),
] ]
@ -25,6 +27,11 @@ class Migration(migrations.Migration):
name='completion_date', name='completion_date',
field=models.DateField(blank=True, null=True, verbose_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( migrations.AlterField(
model_name='build', model_name='build',
name='issued_by', name='issued_by',
@ -35,6 +42,26 @@ class Migration(migrations.Migration):
name='responsible', 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'), 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( migrations.AlterField(
model_name='buildorderattachment', model_name='buildorderattachment',
name='attachment', name='attachment',

View File

@ -216,7 +216,7 @@ class Build(MPTTModel):
help_text=_('Batch code for this build output') 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( target_date = models.DateField(
null=True, blank=True, null=True, blank=True,
@ -1020,14 +1020,14 @@ class BuildItem(models.Model):
try: try:
# Allocated part must be in the BOM for the master part # Allocated part must be in the BOM for the master part
if self.stock_item.part not in self.build.part.getRequiredParts(recursive=False): 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 # Allocated quantity cannot exceed available stock quantity
if self.quantity > self.stock_item.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), n=normalize(self.quantity),
q=normalize(self.stock_item.quantity) q=normalize(self.stock_item.quantity)
))] )]
# Allocated quantity cannot cause the stock item to be over-allocated # 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: if self.stock_item.quantity - self.stock_item.allocation_count() + self.quantity < self.quantity:
@ -1079,6 +1079,7 @@ class BuildItem(models.Model):
Build, Build,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='allocated_stock', related_name='allocated_stock',
verbose_name=_('Build'),
help_text=_('Build to allocate parts') help_text=_('Build to allocate parts')
) )
@ -1086,6 +1087,7 @@ class BuildItem(models.Model):
'stock.StockItem', 'stock.StockItem',
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='allocations', related_name='allocations',
verbose_name=_('Stock Item'),
help_text=_('Source stock item'), help_text=_('Source stock item'),
limit_choices_to={ limit_choices_to={
'sales_order': None, 'sales_order': None,
@ -1098,6 +1100,7 @@ class BuildItem(models.Model):
max_digits=15, max_digits=15,
default=1, default=1,
validators=[MinValueValidator(0)], validators=[MinValueValidator(0)],
verbose_name=_('Quantity'),
help_text=_('Stock quantity to allocate to build') help_text=_('Stock quantity to allocate to build')
) )
@ -1106,6 +1109,7 @@ class BuildItem(models.Model):
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
blank=True, null=True, blank=True, null=True,
related_name='items_to_install', related_name='items_to_install',
verbose_name=_('Install into'),
help_text=_('Destination stock item'), help_text=_('Destination stock item'),
limit_choices_to={ limit_choices_to={
'is_building': True, 'is_building': True,

View File

@ -1,7 +1,12 @@
# Generated by Django 3.0.7 on 2021-04-03 12:10 # Generated by Django 3.0.7 on 2021-04-03 18:37
import InvenTree.fields
import company.models
import django.core.validators import django.core.validators
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion
import markdownx.models
import stdimage.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -11,6 +16,11 @@ class Migration(migrations.Migration):
] ]
operations = [ 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( migrations.AlterField(
model_name='company', model_name='company',
name='is_customer', name='is_customer',
@ -26,6 +36,16 @@ class Migration(migrations.Migration):
name='is_supplier', name='is_supplier',
field=models.BooleanField(default=True, help_text='Do you purchase items from this company?', verbose_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( migrations.AlterField(
model_name='supplierpart', model_name='supplierpart',
name='base_cost', name='base_cost',
@ -41,4 +61,9 @@ class Migration(migrations.Migration):
name='packaging', name='packaging',
field=models.CharField(blank=True, help_text='Part packaging', max_length=50, null=True, verbose_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

@ -114,7 +114,7 @@ class Company(models.Model):
verbose_name=_('Contact'), verbose_name=_('Contact'),
blank=True, help_text=_('Point of 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( image = StdImageField(
upload_to=rename_company_image, upload_to=rename_company_image,
@ -122,9 +122,10 @@ class Company(models.Model):
blank=True, blank=True,
variations={'thumbnail': (128, 128)}, variations={'thumbnail': (128, 128)},
delete_orphans=True, delete_orphans=True,
verbose_name=_('Image'),
) )
notes = MarkdownxField(blank=True) notes = MarkdownxField(blank=True, verbose_name=_('Notes'))
is_customer = models.BooleanField(default=False, verbose_name=_('is customer'), 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?'))
@ -366,11 +367,11 @@ class SupplierPart(models.Model):
help_text=_('Notes') help_text=_('Notes')
) )
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)')) 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, verbose_name=_('Packaging'), 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)], verbose_name=_('multiple'), 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). # TODO - Reimplement lead-time as a charfield with special validation (pattern matching).
# lead_time = models.DurationField(blank=True, null=True) # 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) 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: class Meta:
unique_together = ("part", "quantity") unique_together = ("part", "quantity")

View File

@ -1,14 +1,16 @@
{% extends "modal_delete_form.html" %} {% extends "modal_delete_form.html" %}
{% load i18n %}
{% block pre_form_content %} {% 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> <br>
{% if company.supplied_part_count > 0 %} {% if company.supplied_part_count > 0 %}
<p>There are {{ company.supplied_part_count }} parts sourced from this company.<br> <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.</p> If this supplier is deleted, these supplier part entries will also be deleted.{% endblocktrans %}</p>
<ul class='list-group'> <ul class='list-group'>
{% for part in company.parts.all %} {% for part in company.parts.all %}
<li class='list-group-item'><b>{{ part.SKU }}</b> - <i>{{ part.part.full_name }}</i></li> <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 %} {% if roles.purchase_order.change %}
<div id='button-toolbar'> <div id='button-toolbar'>
<div class='button-toolbar container-fluid'> <div class='button-toolbar container-fluid'>
<div class='btn-group role='group'> <div class='btn-group' role='group'>
{% if roles.purchase_order.add %} {% if roles.purchase_order.add %}
<button class="btn btn-success" id='part-create' title='{% trans "Create new supplier part" %}'> <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" %} <span class='fas fa-plus-circle'></span> {% trans "New Supplier Part" %}

View File

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

@ -24,7 +24,7 @@ from .models import SalesOrderAllocation
class IssuePurchaseOrderForm(HelperForm): 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: class Meta:
model = PurchaseOrder model = PurchaseOrder
@ -35,7 +35,7 @@ class IssuePurchaseOrderForm(HelperForm):
class CompletePurchaseOrderForm(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: class Meta:
model = PurchaseOrder model = PurchaseOrder
@ -46,7 +46,7 @@ class CompletePurchaseOrderForm(HelperForm):
class CancelPurchaseOrderForm(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: class Meta:
model = PurchaseOrder model = PurchaseOrder
@ -57,7 +57,7 @@ class CancelPurchaseOrderForm(HelperForm):
class CancelSalesOrderForm(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: class Meta:
model = SalesOrder model = SalesOrder
@ -68,7 +68,7 @@ class CancelSalesOrderForm(HelperForm):
class ShipSalesOrderForm(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: class Meta:
model = SalesOrder model = SalesOrder
@ -79,7 +79,7 @@ class ShipSalesOrderForm(HelperForm):
class ReceivePurchaseOrderForm(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: class Meta:
model = PurchaseOrder model = PurchaseOrder
@ -106,6 +106,7 @@ class EditPurchaseOrderForm(HelperForm):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
target_date = DatePickerFormField( target_date = DatePickerFormField(
label=_('Target Date'),
help_text=_('Target date for order delivery. Order will be overdue after this 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) super().__init__(*args, **kwargs)
target_date = DatePickerFormField( target_date = DatePickerFormField(
label=_('Target Date'),
help_text=_('Target date for order completion. Order will be overdue after this 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): class EditPurchaseOrderLineItemForm(HelperForm):
""" Form for editing a PurchaseOrderLineItem object """ """ 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: class Meta:
model = PurchaseOrderLineItem model = PurchaseOrderLineItem
@ -200,7 +202,7 @@ class EditPurchaseOrderLineItemForm(HelperForm):
class EditSalesOrderLineItemForm(HelperForm): class EditSalesOrderLineItemForm(HelperForm):
""" Form for editing a SalesOrderLineItem object """ """ 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: class Meta:
model = SalesOrderLineItem model = SalesOrderLineItem
@ -256,7 +258,7 @@ class CreateSalesOrderAllocationForm(HelperForm):
Form for creating a SalesOrderAllocation item. 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: class Meta:
model = SalesOrderAllocation model = SalesOrderAllocation
@ -273,7 +275,7 @@ class EditSalesOrderAllocationForm(HelperForm):
Form for editing a SalesOrderAllocation item 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: class Meta:
model = SalesOrderAllocation model = SalesOrderAllocation

View File

@ -1,57 +0,0 @@
# Generated by Django 3.0.7 on 2021-04-03 12:10
import InvenTree.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('order', '0043_auto_20210330_0013'),
]
operations = [
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='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'),
),
]

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

@ -96,18 +96,19 @@ class Order(models.Model):
class Meta: class Meta:
abstract = True 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, created_by = models.ForeignKey(User,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
blank=True, null=True, blank=True, null=True,
related_name='+' related_name='+',
verbose_name=_('Created By')
) )
responsible = models.ForeignKey( responsible = models.ForeignKey(
@ -119,7 +120,7 @@ class Order(models.Model):
related_name='+', related_name='+',
) )
notes = MarkdownxField(blank=True, help_text=_('Order notes')) notes = MarkdownxField(blank=True, verbose_name=_('Notes'), help_text=_('Order notes'))
class PurchaseOrder(Order): class PurchaseOrder(Order):
@ -186,16 +187,18 @@ class PurchaseOrder(Order):
'is_supplier': True, 'is_supplier': True,
}, },
related_name='purchase_orders', related_name='purchase_orders',
verbose_name=_('Supplier'),
help_text=_('Company from which the items are being ordered') 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( received_by = models.ForeignKey(
User, User,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
blank=True, null=True, blank=True, null=True,
related_name='+' related_name='+',
verbose_name=_('received by')
) )
issue_date = models.DateField( issue_date = models.DateField(
@ -434,13 +437,14 @@ class SalesOrder(Order):
null=True, null=True,
limit_choices_to={'is_customer': True}, limit_choices_to={'is_customer': True},
related_name='sales_orders', related_name='sales_orders',
verbose_name=_('Customer'),
help_text=_("Company to which the items are being sold"), help_text=_("Company to which the items are being sold"),
) )
status = models.PositiveIntegerField(default=SalesOrderStatus.PENDING, choices=SalesOrderStatus.items(), 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( target_date = models.DateField(
null=True, blank=True, 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.') 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( shipped_by = models.ForeignKey(
User, User,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
blank=True, null=True, blank=True, null=True,
related_name='+' related_name='+',
verbose_name=_('shipped by')
) )
@property @property
@ -585,11 +590,11 @@ class OrderLineItem(models.Model):
class Meta: class Meta:
abstract = True 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): class PurchaseOrderLineItem(OrderLineItem):
@ -615,6 +620,7 @@ class PurchaseOrderLineItem(OrderLineItem):
order = models.ForeignKey( order = models.ForeignKey(
PurchaseOrder, on_delete=models.CASCADE, PurchaseOrder, on_delete=models.CASCADE,
related_name='lines', related_name='lines',
verbose_name=_('Order'),
help_text=_('Purchase Order') help_text=_('Purchase Order')
) )
@ -628,10 +634,11 @@ class PurchaseOrderLineItem(OrderLineItem):
SupplierPart, on_delete=models.SET_NULL, SupplierPart, on_delete=models.SET_NULL,
blank=True, null=True, blank=True, null=True,
related_name='purchase_order_line_items', related_name='purchase_order_line_items',
verbose_name=_('Part'),
help_text=_("Supplier 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( purchase_price = MoneyField(
max_digits=19, max_digits=19,
@ -657,9 +664,9 @@ class SalesOrderLineItem(OrderLineItem):
part: Link to a Part object (may be null) 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: class Meta:
unique_together = [ unique_together = [
@ -759,7 +766,7 @@ class SalesOrderAllocation(models.Model):
if len(errors) > 0: if len(errors) > 0:
raise ValidationError(errors) 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( item = models.ForeignKey(
'stock.StockItem', 'stock.StockItem',
@ -770,10 +777,11 @@ class SalesOrderAllocation(models.Model):
'belongs_to': None, 'belongs_to': None,
'sales_order': None, 'sales_order': None,
}, },
verbose_name=_('Item'),
help_text=_('Select stock item to allocate') 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): def get_serial(self):
return self.item.serial return self.item.serial

View File

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

View File

@ -1,7 +1,9 @@
{% extends "modal_form.html" %} {% extends "modal_form.html" %}
{% load i18n %}
{% block pre_form_content %} {% 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 %} {% endblock %}

View File

@ -39,7 +39,7 @@
{{ part.full_name }} <small><i>{{ part.description }}</i></small> {{ part.full_name }} <small><i>{{ part.description }}</i></small>
</td> </td>
<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> <span part='{{ part.pk }}' class='fas fa-plus-circle'></span>
</button> </button>
</td> </td>

View File

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

View File

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

View File

@ -129,6 +129,7 @@ class BomDuplicateForm(HelperForm):
confirm = forms.BooleanField( confirm = forms.BooleanField(
required=False, initial=False, required=False, initial=False,
label=_('Confirm'),
help_text=_('Confirm BOM duplication') help_text=_('Confirm BOM duplication')
) )
@ -147,7 +148,7 @@ class BomValidateForm(HelperForm):
to confirm that the BOM for this part is valid 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: class Meta:
model = Part model = Part
@ -159,7 +160,7 @@ class BomValidateForm(HelperForm):
class BomUploadSelectFile(HelperForm): class BomUploadSelectFile(HelperForm):
""" Form for importing a BOM. Provides a file input box for upload """ """ 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: class Meta:
model = Part model = Part
@ -336,9 +337,9 @@ class EditCategoryParameterTemplateForm(HelperForm):
class EditBomItemForm(HelperForm): class EditBomItemForm(HelperForm):
""" Form for editing a BomItem object """ """ 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: class Meta:
model = BomItem model = BomItem
@ -365,6 +366,7 @@ class PartPriceForm(forms.Form):
quantity = forms.IntegerField( quantity = forms.IntegerField(
required=True, required=True,
initial=1, initial=1,
label=_('Quantity'),
help_text=_('Input quantity for price calculation') help_text=_('Input quantity for price calculation')
) )
@ -380,7 +382,7 @@ class EditPartSalePriceBreakForm(HelperForm):
Form for creating / editing a sale price for a part 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: class Meta:
model = PartSellPriceBreak model = PartSellPriceBreak

View File

@ -1,110 +0,0 @@
# Generated by Django 3.0.7 on 2021-04-03 12:10
import InvenTree.models
import InvenTree.validators
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import mptt.fields
class Migration(migrations.Migration):
dependencies = [
('stock', '0059_auto_20210403_1210'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('part', '0063_bomitem_inherited'),
]
operations = [
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='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='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='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'),
),
]

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

@ -443,10 +443,10 @@ class Part(MPTTModel):
return return
if self.pk == parent.pk: 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), p1=str(self),
p2=str(parent) p2=str(parent)
))}) )})
bom_items = self.get_bom_items() bom_items = self.get_bom_items()
@ -455,10 +455,10 @@ class Part(MPTTModel):
# Check for simple match # Check for simple match
if item.sub_part == parent: 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), p1=str(parent),
p2=str(self) p2=str(self)
))}) )})
# And recursively check too # And recursively check too
item.sub_part.checkAddToBOM(parent) item.sub_part.checkAddToBOM(parent)
@ -750,6 +750,7 @@ class Part(MPTTModel):
blank=True, blank=True,
variations={'thumbnail': (128, 128)}, variations={'thumbnail': (128, 128)},
delete_orphans=False, delete_orphans=False,
verbose_name=_('Image'),
) )
default_location = TreeForeignKey( default_location = TreeForeignKey(
@ -1852,7 +1853,7 @@ class PartAttachment(InvenTreeAttachment):
return os.path.join("part_files", str(self.part.id)) return os.path.join("part_files", str(self.part.id))
part = models.ForeignKey(Part, on_delete=models.CASCADE, part = models.ForeignKey(Part, on_delete=models.CASCADE,
related_name='attachments') verbose_name=_('Part'), related_name='attachments')
class PartSellPriceBreak(common.models.PriceBreak): class PartSellPriceBreak(common.models.PriceBreak):
@ -1863,7 +1864,8 @@ class PartSellPriceBreak(common.models.PriceBreak):
part = models.ForeignKey( part = models.ForeignKey(
Part, on_delete=models.CASCADE, Part, on_delete=models.CASCADE,
related_name='salepricebreaks', related_name='salepricebreaks',
limit_choices_to={'salable': True} limit_choices_to={'salable': True},
verbose_name=_('Part')
) )
class Meta: class Meta:
@ -1881,9 +1883,9 @@ class PartStar(models.Model):
user: Link to a User object 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: class Meta:
unique_together = ['part', 'user'] unique_together = ['part', 'user']
@ -1956,6 +1958,7 @@ class PartTestTemplate(models.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='test_templates', related_name='test_templates',
limit_choices_to={'trackable': True}, limit_choices_to={'trackable': True},
verbose_name=_('Part'),
) )
test_name = models.CharField( test_name = models.CharField(
@ -2023,9 +2026,9 @@ class PartParameterTemplate(models.Model):
except PartParameterTemplate.DoesNotExist: except PartParameterTemplate.DoesNotExist:
pass 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): class PartParameter(models.Model):
@ -2096,15 +2099,18 @@ class PartCategoryParameterTemplate(models.Model):
category = models.ForeignKey(PartCategory, category = models.ForeignKey(PartCategory,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='parameter_templates', related_name='parameter_templates',
verbose_name=_('Category'),
help_text=_('Part Category')) help_text=_('Part Category'))
parameter_template = models.ForeignKey(PartParameterTemplate, parameter_template = models.ForeignKey(PartParameterTemplate,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='part_categories', related_name='part_categories',
verbose_name=_('Parameter Template'),
help_text=_('Parameter Template')) help_text=_('Parameter Template'))
default_value = models.CharField(max_length=500, default_value = models.CharField(max_length=500,
blank=True, blank=True,
verbose_name=_('Default Value'),
help_text=_('Default Parameter Value')) help_text=_('Default Parameter Value'))
@ -2133,6 +2139,7 @@ class BomItem(models.Model):
# A link to the parent part # A link to the parent part
# Each part will get a reverse lookup field 'bom_items' # Each part will get a reverse lookup field 'bom_items'
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='bom_items', part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='bom_items',
verbose_name=_('Part'),
help_text=_('Select parent part'), help_text=_('Select parent part'),
limit_choices_to={ limit_choices_to={
'assembly': True, 'assembly': True,
@ -2141,26 +2148,28 @@ class BomItem(models.Model):
# A link to the child item (sub-part) # A link to the child item (sub-part)
# Each part will get a reverse lookup field 'used_in' # Each part will get a reverse lookup field 'used_in'
sub_part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='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'), help_text=_('Select part to be used in BOM'),
limit_choices_to={ limit_choices_to={
'component': True, 'component': True,
}) })
# Quantity required # 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], overage = models.CharField(max_length=24, blank=True, validators=[validators.validate_overage],
verbose_name=_('Overage'),
help_text=_('Estimated build wastage quantity (absolute or percentage)') 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 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( inherited = models.BooleanField(
default=False, default=False,
@ -2372,11 +2381,11 @@ class PartRelated(models.Model):
""" Store and handle related parts (eg. mating connector, crimps, etc.) """ """ Store and handle related parts (eg. mating connector, crimps, etc.) """
part_1 = models.ForeignKey(Part, related_name='related_parts_1', 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', part_2 = models.ForeignKey(Part, related_name='related_parts_2',
on_delete=models.DO_NOTHING, on_delete=models.DO_NOTHING,
help_text=_('Select Related Part')) verbose_name=_('Part 2'), help_text=_('Select Related Part'))
def __str__(self): def __str__(self):
return f'{self.part_1} <--> {self.part_2}' return f'{self.part_1} <--> {self.part_2}'

View File

@ -16,13 +16,13 @@
<div class='alert alert-block alert-info'> <div class='alert alert-block alert-info'>
{% else %} {% else %}
<div class='alert alert-block alert-danger'> <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 %} {% 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> </div>
{% else %} {% else %}
<div class='alert alert-danger alert-block'> <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> </div>
{% endif %} {% endif %}

View File

@ -24,7 +24,7 @@
</div> </div>
<form method="post" action='' class='js-modal-form' enctype="multipart/form-data"> <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 %} {% csrf_token %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}

View File

@ -1,10 +1,12 @@
{% extends "modal_form.html" %} {% extends "modal_form.html" %}
{% load i18n %}
{% block pre_form_content %} {% 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'> <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> </div>
{% endblock %} {% endblock %}

View File

@ -342,7 +342,7 @@ class PartSetCategory(AjaxUpdateView):
data = { data = {
'form_valid': valid, '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: if valid:

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( snippet = models.FileField(
upload_to=rename_snippet, upload_to=rename_snippet,
verbose_name=_('Snippet'),
help_text=_('Report snippet file'), help_text=_('Report snippet file'),
validators=[FileExtensionValidator(allowed_extensions=['html', 'htm'])], 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): def rename_asset(instance, filename):
@ -536,7 +537,8 @@ class ReportAsset(models.Model):
asset = models.FileField( asset = models.FileField(
upload_to=rename_asset, upload_to=rename_asset,
verbose_name=_('Asset'),
help_text=_("Report asset file"), 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

@ -195,7 +195,7 @@ class StockCount(StockAdjust):
if item['item'].stocktake(item['quantity'], request.user, notes=self.notes): if item['item'].stocktake(item['quantity'], request.user, notes=self.notes):
n += 1 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): class StockAdd(StockAdjust):
@ -264,7 +264,7 @@ class StockTransfer(StockAdjust):
if item['item'].move(location, self.notes, request.user, quantity=item['quantity']): if item['item'].move(location, self.notes, request.user, quantity=item['quantity']):
n += 1 n += 1
return Response({'success': 'Moved {n} parts to {loc}'.format( return Response({'success': _('Moved {n} parts to {loc}').format(
n=n, n=n,
loc=str(location), loc=str(location),
)}) )})

View File

@ -111,7 +111,8 @@ class CreateStockItemForm(HelperForm):
""" Form for creating a new StockItem """ """ Form for creating a new StockItem """
expiry_date = DatePickerFormField( 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)'))
@ -165,13 +166,13 @@ class CreateStockItemForm(HelperForm):
class SerializeStockForm(HelperForm): class SerializeStockForm(HelperForm):
""" Form for serializing a StockItem. """ """ 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): 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')) 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: class Meta:
model = StockLocation model = StockLocation
@ -402,7 +403,8 @@ class EditStockItemForm(HelperForm):
""" """
expiry_date = DatePickerFormField( expiry_date = DatePickerFormField(
help_text=('Expiration date for this stock item'), label=_('Expiry Date'),
help_text=_('Expiration date for this stock item'),
) )
class Meta: class Meta:

View File

@ -1,9 +1,10 @@
# Generated by Django 3.0.7 on 2021-04-03 12:10 # Generated by Django 3.0.7 on 2021-04-04 20:16
import InvenTree.fields import InvenTree.fields
import InvenTree.models import InvenTree.models
import InvenTree.validators import InvenTree.validators
from django.conf import settings from django.conf import settings
import django.core.validators
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import mptt.fields import mptt.fields
@ -12,11 +13,22 @@ import mptt.fields
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('users', '0005_owner_model'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('stock', '0058_stockitem_packaging'), ('stock', '0058_stockitem_packaging'),
] ]
operations = [ 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( migrations.AlterField(
model_name='stockitemattachment', model_name='stockitemattachment',
name='attachment', name='attachment',
@ -47,6 +59,11 @@ class Migration(migrations.Migration):
name='notes', name='notes',
field=models.CharField(blank=True, help_text='Entry notes', max_length=512, verbose_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( migrations.AlterField(
model_name='stockitemtracking', model_name='stockitemtracking',
name='title', name='title',
@ -62,6 +79,11 @@ class Migration(migrations.Migration):
name='name', name='name',
field=models.CharField(help_text='Name', max_length=100, validators=[InvenTree.validators.validate_tree_name], verbose_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( migrations.AlterField(
model_name='stocklocation', model_name='stocklocation',
name='parent', name='parent',

View File

@ -51,7 +51,8 @@ class StockLocation(InvenTreeTree):
""" """
owner = models.ForeignKey(Owner, on_delete=models.SET_NULL, blank=True, null=True, 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') related_name='stock_locations')
def get_absolute_url(self): def get_absolute_url(self):
@ -483,7 +484,7 @@ class StockItem(MPTTModel):
review_needed = models.BooleanField(default=False) 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( status = models.PositiveIntegerField(
default=StockStatus.OK, default=StockStatus.OK,
@ -507,7 +508,8 @@ class StockItem(MPTTModel):
) )
owner = models.ForeignKey(Owner, on_delete=models.SET_NULL, blank=True, null=True, 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') related_name='stock_items')
def is_stale(self): def is_stale(self):
@ -948,7 +950,7 @@ class StockItem(MPTTModel):
raise ValidationError({"quantity": _("Quantity must be greater than zero")}) raise ValidationError({"quantity": _("Quantity must be greater than zero")})
if quantity > self.quantity: 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]: if not type(serials) in [list, tuple]:
raise ValidationError({"serial_numbers": _("Serial numbers must be a list of integers")}) 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) new_item.addTransactionNote(_('Add serial number'), user, notes=notes)
# Remove the equivalent number of items # 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 @transaction.atomic
def copyHistoryFrom(self, other): def copyHistoryFrom(self, other):
@ -1558,7 +1560,7 @@ class StockItemTracking(models.Model):
system = models.BooleanField(default=False) 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 # TODO
# image = models.ImageField(upload_to=func, max_length=255, null=True, blank=True) # image = models.ImageField(upload_to=func, max_length=255, null=True, blank=True)

View File

@ -965,7 +965,7 @@ class StockAdjust(AjaxView, FormMixin):
context['stock_action'] = self.stock_action.strip().lower() 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 # Quantity column will be read-only in some circumstances
context['edit_quantity'] = not self.stock_action == 'delete' 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']: if self.stock_action not in ['move', 'count', 'take', 'add', 'delete']:
self.stock_action = 'count' self.stock_action = 'count'
# Choose the form title based on the action # Choose form title and action column based on the action
titles = { titles = {
'move': _('Move Stock Items'), 'move': [_('Move Stock Items'), _('Move')],
'count': _('Count Stock Items'), 'count': [_('Count Stock Items'), _('Count')],
'take': _('Remove From Stock'), 'take': [_('Remove From Stock'), _('Take')],
'add': _('Add Stock Items'), 'add': [_('Add Stock Items'), _('Add')],
'delete': _('Delete Stock Items') '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! # Save list of items!
self.stock_items = self.get_GET_items() self.stock_items = self.get_GET_items()
@ -1039,7 +1040,7 @@ class StockAdjust(AjaxView, FormMixin):
if self.stock_action in ['move', 'take']: if self.stock_action in ['move', 'take']:
if item.new_quantity > item.quantity: 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 valid = False
continue continue
@ -1118,7 +1119,7 @@ class StockAdjust(AjaxView, FormMixin):
count += 1 count += 1
return f"{_('Added stock to ')} {count} {_('items')}" return _('Added stock to {n} items').format(n=count)
def do_take(self): def do_take(self):
@ -1133,7 +1134,7 @@ class StockAdjust(AjaxView, FormMixin):
count += 1 count += 1
return f"{_('Removed stock from ')} {count} {_('items')}" return _('Removed stock from {n} items').format(n=count)
def do_count(self): def do_count(self):
@ -1189,9 +1190,9 @@ class StockAdjust(AjaxView, FormMixin):
return _('No items were moved') return _('No items were moved')
else: else:
return _('Moved {n} items to {dest}'.format( return _('Moved {n} items to {dest}').format(
n=count, n=count,
dest=destination.pathstring)) dest=destination.pathstring)
def do_delete(self): def do_delete(self):
""" Delete multiple stock items """ """ Delete multiple stock items """
@ -1208,7 +1209,7 @@ class StockAdjust(AjaxView, FormMixin):
count += 1 count += 1
return _("Deleted {n} stock items".format(n=count)) return _("Deleted {n} stock items").format(n=count)
class StockItemEdit(AjaxUpdateView): class StockItemEdit(AjaxUpdateView):