Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2021-01-03 23:34:50 +11:00
commit d5fcb83ad1
11 changed files with 947 additions and 763 deletions

View File

@ -38,7 +38,9 @@ class CompanyAdmin(ImportExportModelAdmin):
class SupplierPartResource(ModelResource): class SupplierPartResource(ModelResource):
""" Class for managing SupplierPart data import/export """ """
Class for managing SupplierPart data import/export
"""
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part)) part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
@ -51,7 +53,7 @@ class SupplierPartResource(ModelResource):
class Meta: class Meta:
model = SupplierPart model = SupplierPart
skip_unchanged = True skip_unchanged = True
report_skipped = False report_skipped = True
clean_model_instances = True clean_model_instances = True

View File

@ -0,0 +1,61 @@
# Generated by Django 3.0.7 on 2021-01-03 11:15
import InvenTree.fields
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('part', '0060_merge_20201112_1722'),
('company', '0030_auto_20201112_1112'),
]
operations = [
migrations.AlterField(
model_name='supplierpart',
name='MPN',
field=models.CharField(blank=True, help_text='Manufacturer part number', max_length=100, null=True, verbose_name='MPN'),
),
migrations.AlterField(
model_name='supplierpart',
name='SKU',
field=models.CharField(help_text='Supplier stock keeping unit', max_length=100, verbose_name='SKU'),
),
migrations.AlterField(
model_name='supplierpart',
name='description',
field=models.CharField(blank=True, help_text='Supplier part description', max_length=250, null=True, verbose_name='Description'),
),
migrations.AlterField(
model_name='supplierpart',
name='link',
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='URL for external supplier part link', null=True, verbose_name='Link'),
),
migrations.AlterField(
model_name='supplierpart',
name='manufacturer',
field=models.ForeignKey(blank=True, help_text='Select manufacturer', limit_choices_to={'is_manufacturer': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='manufactured_parts', to='company.Company', verbose_name='Manufacturer'),
),
migrations.AlterField(
model_name='supplierpart',
name='note',
field=models.CharField(blank=True, help_text='Notes', max_length=100, null=True, verbose_name='Note'),
),
migrations.AlterField(
model_name='supplierpart',
name='packaging',
field=models.CharField(blank=True, help_text='Part packaging', max_length=50, null=True),
),
migrations.AlterField(
model_name='supplierpart',
name='part',
field=models.ForeignKey(help_text='Select part', limit_choices_to={'purchaseable': True}, on_delete=django.db.models.deletion.CASCADE, related_name='supplier_parts', to='part.Part', verbose_name='Base Part'),
),
migrations.AlterField(
model_name='supplierpart',
name='supplier',
field=models.ForeignKey(help_text='Select supplier', limit_choices_to={'is_supplier': True}, on_delete=django.db.models.deletion.CASCADE, related_name='supplied_parts', to='company.Company', verbose_name='Supplier'),
),
]

View File

@ -313,7 +313,6 @@ class SupplierPart(models.Model):
verbose_name=_('Base Part'), verbose_name=_('Base Part'),
limit_choices_to={ limit_choices_to={
'purchaseable': True, 'purchaseable': True,
'is_template': False,
}, },
help_text=_('Select part'), help_text=_('Select part'),
) )
@ -321,31 +320,55 @@ class SupplierPart(models.Model):
supplier = models.ForeignKey(Company, on_delete=models.CASCADE, supplier = models.ForeignKey(Company, on_delete=models.CASCADE,
related_name='supplied_parts', related_name='supplied_parts',
limit_choices_to={'is_supplier': True}, limit_choices_to={'is_supplier': True},
verbose_name=_('Supplier'),
help_text=_('Select supplier'), help_text=_('Select supplier'),
) )
SKU = models.CharField(max_length=100, help_text=_('Supplier stock keeping unit')) SKU = models.CharField(
max_length=100,
verbose_name=_('SKU'),
help_text=_('Supplier stock keeping unit')
)
manufacturer = models.ForeignKey( manufacturer = models.ForeignKey(
Company, Company,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
related_name='manufactured_parts', related_name='manufactured_parts',
limit_choices_to={'is_manufacturer': True}, limit_choices_to={
'is_manufacturer': True
},
verbose_name=_('Manufacturer'),
help_text=_('Select manufacturer'), help_text=_('Select manufacturer'),
null=True, blank=True null=True, blank=True
) )
MPN = models.CharField(max_length=100, blank=True, help_text=_('Manufacturer part number')) MPN = models.CharField(
max_length=100, blank=True, null=True,
verbose_name=_('MPN'),
help_text=_('Manufacturer part number')
)
link = InvenTreeURLField(blank=True, help_text=_('URL for external supplier part link')) link = InvenTreeURLField(
blank=True, null=True,
verbose_name=_('Link'),
help_text=_('URL for external supplier part link')
)
description = models.CharField(max_length=250, blank=True, help_text=_('Supplier part description')) description = models.CharField(
max_length=250, blank=True, null=True,
verbose_name=_('Description'),
help_text=_('Supplier part description')
)
note = models.CharField(max_length=100, blank=True, help_text=_('Notes')) note = models.CharField(
max_length=100, blank=True, null=True,
verbose_name=_('Note'),
help_text=_('Notes')
)
base_cost = models.DecimalField(max_digits=10, decimal_places=3, default=0, validators=[MinValueValidator(0)], help_text=_('Minimum charge (e.g. stocking fee)')) base_cost = models.DecimalField(max_digits=10, decimal_places=3, default=0, validators=[MinValueValidator(0)], help_text=_('Minimum charge (e.g. stocking fee)'))
packaging = models.CharField(max_length=50, blank=True, help_text=_('Part packaging')) packaging = models.CharField(max_length=50, blank=True, null=True, help_text=_('Part packaging'))
multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)], help_text=('Order multiple')) multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)], help_text=('Order multiple'))

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

@ -319,7 +319,9 @@ class EditBomItemForm(HelperForm):
] ]
# Prevent editing of the part associated with this BomItem # Prevent editing of the part associated with this BomItem
widgets = {'part': forms.HiddenInput()} widgets = {
'part': forms.HiddenInput()
}
class PartPriceForm(forms.Form): class PartPriceForm(forms.Form):

View File

@ -1074,7 +1074,7 @@ class Part(MPTTModel):
self.bom_items.all().delete() self.bom_items.all().delete()
def getRequiredParts(self, recursive=False, parts=set()): def getRequiredParts(self, recursive=False, parts=None):
""" """
Return a list of parts required to make this part (i.e. BOM items). Return a list of parts required to make this part (i.e. BOM items).
@ -1083,7 +1083,10 @@ class Part(MPTTModel):
parts: Set of parts already found (to prevent recursion issues) parts: Set of parts already found (to prevent recursion issues)
""" """
items = self.bom_items.all().prefetch_related('sub_part') if parts is None:
parts = set()
items = BomItem.objects.filter(part=self.pk)
for bom_item in items: for bom_item in items:

View File

@ -32,7 +32,7 @@
<div class="btn-group" role="group" aria-label="..."> <div class="btn-group" role="group" aria-label="...">
{% if editing_enabled %} {% if editing_enabled %}
<button class='btn btn-default' type='button' title='{% trans "Remove selected BOM items" %}' id='bom-item-delete'> <button class='btn btn-default' type='button' title='{% trans "Remove selected BOM items" %}' id='bom-item-delete'>
<span class='fas fa-trash-alt'></span> <span class='fas fa-trash-alt icon-red'></span>
</button> </button>
<button class='btn btn-primary' type='button' title='{% trans "Import BOM data" %}' id='bom-upload'> <button class='btn btn-primary' type='button' title='{% trans "Import BOM data" %}' id='bom-upload'>
<span class='fas fa-file-upload'></span> {% trans "Import from File" %} <span class='fas fa-file-upload'></span> {% trans "Import from File" %}

View File

@ -1307,7 +1307,8 @@ class StockItemEdit(AjaxUpdateView):
# If the part cannot be purchased, hide the supplier_part field # If the part cannot be purchased, hide the supplier_part field
if not item.part.purchaseable: if not item.part.purchaseable:
form.fields['supplier_part'].widget = HiddenInput() form.fields['supplier_part'].widget = HiddenInput()
form.fields['purchase_price'].widget = HiddenInput()
form.fields.pop('purchase_price')
else: else:
query = form.fields['supplier_part'].queryset query = form.fields['supplier_part'].queryset
query = query.filter(part=item.part.id) query = query.filter(part=item.part.id)
@ -1522,7 +1523,7 @@ class StockItemCreate(AjaxCreateView):
form.rebuild_layout() form.rebuild_layout()
if not part.purchaseable: if not part.purchaseable:
form.fields['purchase_price'].widget = HiddenInput() form.fields.pop('purchase_price')
# Hide the 'part' field (as a valid part is selected) # Hide the 'part' field (as a valid part is selected)
# form.fields['part'].widget = HiddenInput() # form.fields['part'].widget = HiddenInput()

View File

@ -278,7 +278,7 @@ function loadStockTable(table, options) {
if (row.is_building && row.build) { if (row.is_building && row.build) {
// StockItem is currently being built! // StockItem is currently being built!
text = "{% trans "In production" %}"; text = '{% trans "In production" %}';
url = `/build/${row.build}/`; url = `/build/${row.build}/`;
} else if (row.belongs_to) { } else if (row.belongs_to) {
// StockItem is installed inside a different StockItem // StockItem is installed inside a different StockItem
@ -286,17 +286,17 @@ function loadStockTable(table, options) {
url = `/stock/item/${row.belongs_to}/installed/`; url = `/stock/item/${row.belongs_to}/installed/`;
} else if (row.customer) { } else if (row.customer) {
// StockItem has been assigned to a customer // StockItem has been assigned to a customer
text = "{% trans "Shipped to customer" %}"; text = '{% trans "Shipped to customer" %}';
url = `/company/${row.customer}/assigned-stock/`; url = `/company/${row.customer}/assigned-stock/`;
} else if (row.sales_order) { } else if (row.sales_order) {
// StockItem has been assigned to a sales order // StockItem has been assigned to a sales order
text = "{% trans "Assigned to Sales Order" %}"; text = '{% trans "Assigned to Sales Order" %}';
url = `/order/sales-order/${row.sales_order}/`; url = `/order/sales-order/${row.sales_order}/`;
} else if (row.location) { } else if (row.location) {
text = row.location_detail.pathstring; text = row.location_detail.pathstring;
url = `/stock/location/${row.location}/`; url = `/stock/location/${row.location}/`;
} else { } else {
text = "<i>{% trans "No stock location set" %}</i>"; text = '<i>{% trans "No stock location set" %}</i>';
url = ''; url = '';
} }
@ -336,7 +336,13 @@ function loadStockTable(table, options) {
return html; return html;
} }
else if (field == 'part_detail.IPN') { else if (field == 'part_detail.IPN') {
return row.part_detail.IPN; var ipn = row.part_detail.IPN;
if (ipn) {
return ipn;
} else {
return '-';
}
} }
else if (field == 'part_detail.description') { else if (field == 'part_detail.description') {
return row.part_detail.description; return row.part_detail.description;