mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-20 22:06:28 +00:00
Merge branch 'master' of github.com:inventree/InvenTree into manufacturer_part
This commit is contained in:
@ -275,7 +275,7 @@ class BomUploadManager:
|
||||
elif ext in ['.xls', '.xlsx']:
|
||||
raw_data = bom_file.read()
|
||||
else:
|
||||
raise ValidationError({'bom_file': _('Unsupported file format: {f}'.format(f=ext))})
|
||||
raise ValidationError({'bom_file': _('Unsupported file format: {f}').format(f=ext)})
|
||||
|
||||
try:
|
||||
self.data = tablib.Dataset().load(raw_data)
|
||||
|
@ -11,7 +11,7 @@ from InvenTree.fields import RoundingDecimalFormField
|
||||
|
||||
from mptt.fields import TreeNodeChoiceField
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import common.models
|
||||
|
||||
@ -129,6 +129,7 @@ class BomDuplicateForm(HelperForm):
|
||||
|
||||
confirm = forms.BooleanField(
|
||||
required=False, initial=False,
|
||||
label=_('Confirm'),
|
||||
help_text=_('Confirm BOM duplication')
|
||||
)
|
||||
|
||||
@ -147,7 +148,7 @@ class BomValidateForm(HelperForm):
|
||||
to confirm that the BOM for this part is valid
|
||||
"""
|
||||
|
||||
validate = forms.BooleanField(required=False, initial=False, help_text=_('Confirm that the BOM is correct'))
|
||||
validate = forms.BooleanField(required=False, initial=False, label=_('validate'), help_text=_('Confirm that the BOM is correct'))
|
||||
|
||||
class Meta:
|
||||
model = Part
|
||||
@ -159,7 +160,7 @@ class BomValidateForm(HelperForm):
|
||||
class BomUploadSelectFile(HelperForm):
|
||||
""" Form for importing a BOM. Provides a file input box for upload """
|
||||
|
||||
bom_file = forms.FileField(label='BOM file', required=True, help_text=_("Select BOM file to upload"))
|
||||
bom_file = forms.FileField(label=_('BOM file'), required=True, help_text=_("Select BOM file to upload"))
|
||||
|
||||
class Meta:
|
||||
model = Part
|
||||
@ -336,9 +337,9 @@ class EditCategoryParameterTemplateForm(HelperForm):
|
||||
class EditBomItemForm(HelperForm):
|
||||
""" Form for editing a BomItem object """
|
||||
|
||||
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5)
|
||||
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity'))
|
||||
|
||||
sub_part = PartModelChoiceField(queryset=Part.objects.all())
|
||||
sub_part = PartModelChoiceField(queryset=Part.objects.all(), label=_('Sub part'))
|
||||
|
||||
class Meta:
|
||||
model = BomItem
|
||||
@ -365,6 +366,7 @@ class PartPriceForm(forms.Form):
|
||||
quantity = forms.IntegerField(
|
||||
required=True,
|
||||
initial=1,
|
||||
label=_('Quantity'),
|
||||
help_text=_('Input quantity for price calculation')
|
||||
)
|
||||
|
||||
@ -380,7 +382,7 @@ class EditPartSalePriceBreakForm(HelperForm):
|
||||
Form for creating / editing a sale price for a part
|
||||
"""
|
||||
|
||||
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5)
|
||||
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity'))
|
||||
|
||||
class Meta:
|
||||
model = PartSellPriceBreak
|
||||
|
218
InvenTree/part/migrations/0064_auto_20210404_2016.py
Normal file
218
InvenTree/part/migrations/0064_auto_20210404_2016.py
Normal 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'),
|
||||
),
|
||||
]
|
@ -69,10 +69,11 @@ class PartCategory(InvenTreeTree):
|
||||
'stock.StockLocation', related_name="default_categories",
|
||||
null=True, blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name=_('Default Location'),
|
||||
help_text=_('Default location for parts in this category')
|
||||
)
|
||||
|
||||
default_keywords = models.CharField(null=True, blank=True, max_length=250, help_text=_('Default keywords for parts in this category'))
|
||||
default_keywords = models.CharField(null=True, blank=True, max_length=250, verbose_name=_('Default keywords'), help_text=_('Default keywords for parts in this category'))
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('category-detail', kwargs={'pk': self.id})
|
||||
@ -442,10 +443,10 @@ class Part(MPTTModel):
|
||||
return
|
||||
|
||||
if self.pk == parent.pk:
|
||||
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)".format(
|
||||
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)").format(
|
||||
p1=str(self),
|
||||
p2=str(parent)
|
||||
))})
|
||||
)})
|
||||
|
||||
bom_items = self.get_bom_items()
|
||||
|
||||
@ -454,10 +455,10 @@ class Part(MPTTModel):
|
||||
|
||||
# Check for simple match
|
||||
if item.sub_part == parent:
|
||||
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)".format(
|
||||
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)").format(
|
||||
p1=str(parent),
|
||||
p2=str(self)
|
||||
))})
|
||||
)})
|
||||
|
||||
# And recursively check too
|
||||
item.sub_part.checkAddToBOM(parent)
|
||||
@ -749,6 +750,7 @@ class Part(MPTTModel):
|
||||
blank=True,
|
||||
variations={'thumbnail': (128, 128)},
|
||||
delete_orphans=False,
|
||||
verbose_name=_('Image'),
|
||||
)
|
||||
|
||||
default_location = TreeForeignKey(
|
||||
@ -870,18 +872,18 @@ class Part(MPTTModel):
|
||||
help_text=_('Part notes - supports Markdown formatting')
|
||||
)
|
||||
|
||||
bom_checksum = models.CharField(max_length=128, blank=True, help_text=_('Stored BOM checksum'))
|
||||
bom_checksum = models.CharField(max_length=128, blank=True, verbose_name=_('BOM checksum'), help_text=_('Stored BOM checksum'))
|
||||
|
||||
bom_checked_by = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True,
|
||||
related_name='boms_checked')
|
||||
verbose_name=_('BOM checked by'), related_name='boms_checked')
|
||||
|
||||
bom_checked_date = models.DateField(blank=True, null=True)
|
||||
bom_checked_date = models.DateField(blank=True, null=True, verbose_name=_('BOM checked date'))
|
||||
|
||||
creation_date = models.DateField(auto_now_add=True, editable=False, blank=True, null=True)
|
||||
creation_date = models.DateField(auto_now_add=True, editable=False, blank=True, null=True, verbose_name=_('Creation Date'))
|
||||
|
||||
creation_user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, related_name='parts_created')
|
||||
creation_user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_('Creation User'), related_name='parts_created')
|
||||
|
||||
responsible = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, related_name='parts_responible')
|
||||
responsible = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_('Responsible'), related_name='parts_responible')
|
||||
|
||||
def format_barcode(self, **kwargs):
|
||||
""" Return a JSON string for formatting a barcode for this Part object """
|
||||
@ -1851,7 +1853,7 @@ class PartAttachment(InvenTreeAttachment):
|
||||
return os.path.join("part_files", str(self.part.id))
|
||||
|
||||
part = models.ForeignKey(Part, on_delete=models.CASCADE,
|
||||
related_name='attachments')
|
||||
verbose_name=_('Part'), related_name='attachments')
|
||||
|
||||
|
||||
class PartSellPriceBreak(common.models.PriceBreak):
|
||||
@ -1862,7 +1864,8 @@ class PartSellPriceBreak(common.models.PriceBreak):
|
||||
part = models.ForeignKey(
|
||||
Part, on_delete=models.CASCADE,
|
||||
related_name='salepricebreaks',
|
||||
limit_choices_to={'salable': True}
|
||||
limit_choices_to={'salable': True},
|
||||
verbose_name=_('Part')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@ -1880,9 +1883,9 @@ class PartStar(models.Model):
|
||||
user: Link to a User object
|
||||
"""
|
||||
|
||||
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='starred_users')
|
||||
part = models.ForeignKey(Part, on_delete=models.CASCADE, verbose_name=_('Part'), related_name='starred_users')
|
||||
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='starred_parts')
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('User'), related_name='starred_parts')
|
||||
|
||||
class Meta:
|
||||
unique_together = ['part', 'user']
|
||||
@ -1955,6 +1958,7 @@ class PartTestTemplate(models.Model):
|
||||
on_delete=models.CASCADE,
|
||||
related_name='test_templates',
|
||||
limit_choices_to={'trackable': True},
|
||||
verbose_name=_('Part'),
|
||||
)
|
||||
|
||||
test_name = models.CharField(
|
||||
@ -2022,9 +2026,9 @@ class PartParameterTemplate(models.Model):
|
||||
except PartParameterTemplate.DoesNotExist:
|
||||
pass
|
||||
|
||||
name = models.CharField(max_length=100, help_text=_('Parameter Name'), unique=True)
|
||||
name = models.CharField(max_length=100, verbose_name=_('Name'), help_text=_('Parameter Name'), unique=True)
|
||||
|
||||
units = models.CharField(max_length=25, help_text=_('Parameter Units'), blank=True)
|
||||
units = models.CharField(max_length=25, verbose_name=_('Units'), help_text=_('Parameter Units'), blank=True)
|
||||
|
||||
|
||||
class PartParameter(models.Model):
|
||||
@ -2050,11 +2054,11 @@ class PartParameter(models.Model):
|
||||
# Prevent multiple instances of a parameter for a single part
|
||||
unique_together = ('part', 'template')
|
||||
|
||||
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='parameters', help_text=_('Parent Part'))
|
||||
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='parameters', verbose_name=_('Part'), help_text=_('Parent Part'))
|
||||
|
||||
template = models.ForeignKey(PartParameterTemplate, on_delete=models.CASCADE, related_name='instances', help_text=_('Parameter Template'))
|
||||
template = models.ForeignKey(PartParameterTemplate, on_delete=models.CASCADE, related_name='instances', verbose_name=_('Template'), help_text=_('Parameter Template'))
|
||||
|
||||
data = models.CharField(max_length=500, help_text=_('Parameter Value'))
|
||||
data = models.CharField(max_length=500, verbose_name=_('Data'), help_text=_('Parameter Value'))
|
||||
|
||||
@classmethod
|
||||
def create(cls, part, template, data, save=False):
|
||||
@ -2095,15 +2099,18 @@ class PartCategoryParameterTemplate(models.Model):
|
||||
category = models.ForeignKey(PartCategory,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='parameter_templates',
|
||||
verbose_name=_('Category'),
|
||||
help_text=_('Part Category'))
|
||||
|
||||
parameter_template = models.ForeignKey(PartParameterTemplate,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='part_categories',
|
||||
verbose_name=_('Parameter Template'),
|
||||
help_text=_('Parameter Template'))
|
||||
|
||||
default_value = models.CharField(max_length=500,
|
||||
blank=True,
|
||||
verbose_name=_('Default Value'),
|
||||
help_text=_('Default Parameter Value'))
|
||||
|
||||
|
||||
@ -2132,6 +2139,7 @@ class BomItem(models.Model):
|
||||
# A link to the parent part
|
||||
# Each part will get a reverse lookup field 'bom_items'
|
||||
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='bom_items',
|
||||
verbose_name=_('Part'),
|
||||
help_text=_('Select parent part'),
|
||||
limit_choices_to={
|
||||
'assembly': True,
|
||||
@ -2140,26 +2148,28 @@ class BomItem(models.Model):
|
||||
# A link to the child item (sub-part)
|
||||
# Each part will get a reverse lookup field 'used_in'
|
||||
sub_part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='used_in',
|
||||
verbose_name=_('Sub part'),
|
||||
help_text=_('Select part to be used in BOM'),
|
||||
limit_choices_to={
|
||||
'component': True,
|
||||
})
|
||||
|
||||
# Quantity required
|
||||
quantity = models.DecimalField(default=1.0, max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], help_text=_('BOM quantity for this BOM item'))
|
||||
quantity = models.DecimalField(default=1.0, max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], verbose_name=_('Quantity'), help_text=_('BOM quantity for this BOM item'))
|
||||
|
||||
optional = models.BooleanField(default=False, help_text=_("This BOM item is optional"))
|
||||
optional = models.BooleanField(default=False, verbose_name=_('Optional'), help_text=_("This BOM item is optional"))
|
||||
|
||||
overage = models.CharField(max_length=24, blank=True, validators=[validators.validate_overage],
|
||||
verbose_name=_('Overage'),
|
||||
help_text=_('Estimated build wastage quantity (absolute or percentage)')
|
||||
)
|
||||
|
||||
reference = models.CharField(max_length=500, blank=True, help_text=_('BOM item reference'))
|
||||
reference = models.CharField(max_length=500, blank=True, verbose_name=_('Reference'), help_text=_('BOM item reference'))
|
||||
|
||||
# Note attached to this BOM line item
|
||||
note = models.CharField(max_length=500, blank=True, help_text=_('BOM item notes'))
|
||||
note = models.CharField(max_length=500, blank=True, verbose_name=_('Note'), help_text=_('BOM item notes'))
|
||||
|
||||
checksum = models.CharField(max_length=128, blank=True, help_text=_('BOM line checksum'))
|
||||
checksum = models.CharField(max_length=128, blank=True, verbose_name=_('Checksum'), help_text=_('BOM line checksum'))
|
||||
|
||||
inherited = models.BooleanField(
|
||||
default=False,
|
||||
@ -2371,11 +2381,11 @@ class PartRelated(models.Model):
|
||||
""" Store and handle related parts (eg. mating connector, crimps, etc.) """
|
||||
|
||||
part_1 = models.ForeignKey(Part, related_name='related_parts_1',
|
||||
on_delete=models.DO_NOTHING)
|
||||
verbose_name=_('Part 1'), on_delete=models.DO_NOTHING)
|
||||
|
||||
part_2 = models.ForeignKey(Part, related_name='related_parts_2',
|
||||
on_delete=models.DO_NOTHING,
|
||||
help_text=_('Select Related Part'))
|
||||
verbose_name=_('Part 2'), help_text=_('Select Related Part'))
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.part_1} <--> {self.part_2}'
|
||||
|
@ -16,13 +16,13 @@
|
||||
<div class='alert alert-block alert-info'>
|
||||
{% else %}
|
||||
<div class='alert alert-block alert-danger'>
|
||||
The BOM for <i>{{ part.full_name }}</i> has changed, and must be validated.<br>
|
||||
{% blocktrans with part=part.full_name %}The BOM for <i>{{ part }}</i> has changed, and must be validated.<br>{% endblocktrans %}
|
||||
{% endif %}
|
||||
The BOM for <i>{{ part.full_name }}</i> was last checked by {{ part.bom_checked_by }} on {{ part.bom_checked_date }}
|
||||
{% blocktrans with part=part.full_name checker=part.bom_checked_by check_date=part.bom_checked_date %}The BOM for <i>{{ part }}</i> was last checked by {{ checker }} on {{ check_date }}{% endblocktrans %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class='alert alert-danger alert-block'>
|
||||
<b>The BOM for <i>{{ part.full_name }}</i> has not been validated.</b>
|
||||
<b>{% blocktrans with part=part.full_name %}The BOM for <i>{{ part }}</i> has not been validated.{% endblocktrans %}</b>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
@ -44,7 +44,7 @@
|
||||
<div>
|
||||
<input type='hidden' name='col_name_{{ forloop.counter0 }}' value='{{ col.name }}'/>
|
||||
{{ col.name }}
|
||||
<button class='btn btn-default btn-remove' onClick='removeColFromBomWizard()' id='del_col_{{ forloop.counter0 }}' style='display: inline; float: right;' title='Remove column'>
|
||||
<button class='btn btn-default btn-remove' onClick='removeColFromBomWizard()' id='del_col_{{ forloop.counter0 }}' style='display: inline; float: right;' title='{% trans "Remove column" %}'>
|
||||
<span col_id='{{ forloop.counter0 }}' class='fas fa-trash-alt icon-red'></span>
|
||||
</button>
|
||||
</div>
|
||||
@ -73,7 +73,7 @@
|
||||
{% for row in bom_rows %}
|
||||
<tr>
|
||||
<td>
|
||||
<button class='btn btn-default btn-remove' onClick='removeRowFromBomWizard()' id='del_row_{{ forloop.counter }}' style='display: inline; float: right;' title='Remove row'>
|
||||
<button class='btn btn-default btn-remove' onClick='removeRowFromBomWizard()' id='del_row_{{ forloop.counter }}' style='display: inline; float: right;' title='{% trans "Remove row" %}'>
|
||||
<span row_id='{{ forloop.counter }}' class='fas fa-trash-alt icon-red'></span>
|
||||
</button>
|
||||
</td>
|
||||
|
@ -24,7 +24,7 @@
|
||||
</div>
|
||||
|
||||
<form method="post" action='' class='js-modal-form' enctype="multipart/form-data">
|
||||
<button type="submit" class="save btn btn-default">Upload File</button>
|
||||
<button type="submit" class="save btn btn-default">{% trans 'Upload File' %}</button>
|
||||
{% csrf_token %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
{% extends "modal_form.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block pre_form_content %}
|
||||
Confirm that the Bill of Materials (BOM) is valid for:<br><i>{{ part.full_name }}</i>
|
||||
{% blocktrans with part.full_name as part %}Confirm that the Bill of Materials (BOM) is valid for:<br><i>{{ part }}</i>{% endblocktrans %}
|
||||
|
||||
<div class='alert alert-warning alert-block'>
|
||||
This will validate each line in the BOM.
|
||||
{% trans 'This will validate each line in the BOM.' %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -65,8 +65,8 @@
|
||||
reload: true,
|
||||
secondary: [{
|
||||
field: 'template',
|
||||
label: 'New Template',
|
||||
title: 'Create New Parameter Template',
|
||||
label: '{% trans "New Template" %}',
|
||||
title: '{% trans "Create New Parameter Template" %}',
|
||||
url: "{% url 'part-param-template-create' %}"
|
||||
}],
|
||||
});
|
||||
|
@ -246,7 +246,7 @@
|
||||
launchModalForm(
|
||||
"{% url 'part-pricing' part.id %}",
|
||||
{
|
||||
submit_text: 'Calculate',
|
||||
submit_text: '{% trans "Calculate" %}',
|
||||
hideErrorMessage: true,
|
||||
}
|
||||
);
|
||||
|
@ -1,35 +1,37 @@
|
||||
{% extends "modal_form.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block pre_form_content %}
|
||||
|
||||
<div class='alert alert-info alert-block'>
|
||||
Pricing information for:<br>
|
||||
{% trans 'Pricing information for:' %}<br>
|
||||
{{ part }}.
|
||||
</div>
|
||||
|
||||
<h4>Quantity</h4>
|
||||
<h4>{% trans 'Quantity' %}</h4>
|
||||
<table class='table table-striped table-condensed'>
|
||||
<tr>
|
||||
<td><b>Part</b></td>
|
||||
<td><b>{% trans 'Part' %}</b></td>
|
||||
<td colspan='2'>{{ part }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Quantity</b></td>
|
||||
<td><b>{% trans 'Quantity' %}</b></td>
|
||||
<td colspan='2'>{{ quantity }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% if part.supplier_count > 0 %}
|
||||
<h4>Supplier Pricing</h4>
|
||||
<h4>{% trans 'Supplier Pricing' %}</h4>
|
||||
<table class='table table-striped table-condensed'>
|
||||
{% if min_total_buy_price %}
|
||||
<tr>
|
||||
<td><b>Unit Cost</b></td>
|
||||
<td><b>{% trans 'Unit Cost' %}</b></td>
|
||||
<td>Min: {% include "price.html" with price=min_unit_buy_price %}</td>
|
||||
<td>Max: {% include "price.html" with price=max_unit_buy_price %}</td>
|
||||
</tr>
|
||||
{% if quantity > 1 %}
|
||||
<tr>
|
||||
<td><b>Total Cost</b></td>
|
||||
<td><b>{% trans 'Total Cost' %}</b></td>
|
||||
<td>Min: {% include "price.html" with price=min_total_buy_price %}</td>
|
||||
<td>Max: {% include "price.html" with price=max_total_buy_price %}</td>
|
||||
</tr>
|
||||
@ -37,7 +39,7 @@ Pricing information for:<br>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan='3'>
|
||||
<span class='warning-msg'><i>No supplier pricing available</i></span>
|
||||
<span class='warning-msg'><i>{% trans 'No supplier pricing available' %}</i></span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@ -45,17 +47,17 @@ Pricing information for:<br>
|
||||
{% endif %}
|
||||
|
||||
{% if part.bom_count > 0 %}
|
||||
<h4>BOM Pricing</h4>
|
||||
<h4>{% trans 'BOM Pricing' %}</h4>
|
||||
<table class='table table-striped table-condensed'>
|
||||
{% if min_total_bom_price %}
|
||||
<tr>
|
||||
<td><b>Unit Cost</b></td>
|
||||
<td><b>{% trans 'Unit Cost' %}</b></td>
|
||||
<td>Min: {% include "price.html" with price=min_unit_bom_price %}</td>
|
||||
<td>Max: {% include "price.html" with price=max_unit_bom_price %}</td>
|
||||
</tr>
|
||||
{% if quantity > 1 %}
|
||||
<tr>
|
||||
<td><b>Total Cost</b></td>
|
||||
<td><b>{% trans 'Total Cost' %}</b></td>
|
||||
<td>Min: {% include "price.html" with price=min_total_bom_price %}</td>
|
||||
<td>Max: {% include "price.html" with price=max_total_bom_price %}</td>
|
||||
</tr>
|
||||
@ -63,14 +65,14 @@ Pricing information for:<br>
|
||||
{% if part.has_complete_bom_pricing == False %}
|
||||
<tr>
|
||||
<td colspan='3'>
|
||||
<span class='warning-msg'><i>Note: BOM pricing is incomplete for this part</i></span>
|
||||
<span class='warning-msg'><i>{% trans 'Note: BOM pricing is incomplete for this part' %}</i></span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan='3'>
|
||||
<span class='warning-msg'><i>No BOM pricing available</i></span>
|
||||
<span class='warning-msg'><i>{% trans 'No BOM pricing available' %}</i></span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@ -80,7 +82,7 @@ Pricing information for:<br>
|
||||
{% if min_unit_buy_price or min_unit_bom_price %}
|
||||
{% else %}
|
||||
<div class='alert alert-danger alert-block'>
|
||||
No pricing information is available for this part.
|
||||
{% trans 'No pricing information is available for this part.' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
@ -342,7 +342,7 @@ class PartSetCategory(AjaxUpdateView):
|
||||
|
||||
data = {
|
||||
'form_valid': valid,
|
||||
'success': _('Set category for {n} parts'.format(n=len(self.parts)))
|
||||
'success': _('Set category for {n} parts').format(n=len(self.parts))
|
||||
}
|
||||
|
||||
if valid:
|
||||
|
Reference in New Issue
Block a user