diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 33a63797ac..823a19ba61 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -225,7 +225,7 @@ class Build(models.Model): 'Removed {n} items to build {m} x {part}'.format( n=item.quantity, m=self.quantity, - part=self.part.name + part=self.part.full_name ) ) @@ -370,7 +370,7 @@ class BuildItem(models.Model): if self.stock_item is not None and self.stock_item.part is not None: if self.stock_item.part not in self.build.part.required_parts(): - errors['stock_item'] = [_("Selected stock item not found in BOM for part '{p}'".format(p=self.build.part.name))] + errors['stock_item'] = [_("Selected stock item not found in BOM for part '{p}'".format(p=self.build.part.full_name))] if self.stock_item is not None and self.quantity > self.stock_item.quantity: errors['quantity'] = [_("Allocated quantity ({n}) must not exceed available quantity ({q})".format( diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 947f3f6dc1..d644cbdc35 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -44,7 +44,7 @@ class BuildItemSerializer(InvenTreeModelSerializer): """ Serializes a BuildItem object """ part = serializers.IntegerField(source='stock_item.part.pk', read_only=True) - part_name = serializers.CharField(source='stock_item.part.name', read_only=True) + part_name = serializers.CharField(source='stock_item.part.full_name', read_only=True) stock_item_detail = StockItemSerializerBrief(source='stock_item', read_only=True) class Meta: diff --git a/InvenTree/build/templates/build/allocate.html b/InvenTree/build/templates/build/allocate.html index 15061a18b4..9a0f045c68 100644 --- a/InvenTree/build/templates/build/allocate.html +++ b/InvenTree/build/templates/build/allocate.html @@ -14,7 +14,7 @@ InvenTree | Allocate Parts

{{ build.title }}

- {{ build.quantity }} x {{ build.part.name }} + {{ build.quantity }} x {{ build.part.lonname }}
@@ -47,7 +47,7 @@ InvenTree | Allocate Parts loadAllocationTable( $("#allocate-table-id-{{ bom_item.sub_part.id }}"), {{ bom_item.sub_part.id }}, - "{{ bom_item.sub_part.name }}", + "{{ bom_item.sub_part.full_name }}", "{% url 'api-build-item-list' %}?build={{ build.id }}&part={{ bom_item.sub_part.id }}", {% multiply build.quantity bom_item.quantity %}, $("#new-item-{{ bom_item.sub_part.id }}") diff --git a/InvenTree/build/templates/build/allocation_item.html b/InvenTree/build/templates/build/allocation_item.html index 52f7b4ef1a..94e9e043a2 100644 --- a/InvenTree/build/templates/build/allocation_item.html +++ b/InvenTree/build/templates/build/allocation_item.html @@ -9,7 +9,7 @@
- {{ item.sub_part.name }}
+ {{ item.sub_part.full_name }}
{{ item.sub_part.description }}
{% endblock %} diff --git a/InvenTree/build/templates/build/auto_allocate.html b/InvenTree/build/templates/build/auto_allocate.html index ba789b9ff7..f850d094b2 100644 --- a/InvenTree/build/templates/build/auto_allocate.html +++ b/InvenTree/build/templates/build/auto_allocate.html @@ -4,7 +4,7 @@ {{ block.super }} -Build: {{ build.title }} - {{ build.quantity }} x {{ build.part.name }} +Build: {{ build.title }} - {{ build.quantity }} x {{ build.part.full_name }}

Automatically allocate stock to this build?
@@ -27,7 +27,7 @@ Automatically allocate stock to this build? - {{ item.stock_item.part.name }}
+ {{ item.stock_item.part.full_name }}
{{ item.stock_item.part.description }} {{ item.quantity }} diff --git a/InvenTree/build/templates/build/build_list.html b/InvenTree/build/templates/build/build_list.html index 6c28f47eda..6b04ee6c8e 100644 --- a/InvenTree/build/templates/build/build_list.html +++ b/InvenTree/build/templates/build/build_list.html @@ -23,7 +23,7 @@ {% for build in builds %} {{ build.title }} - {{ build.part.name }} + {{ build.part.full_name }} {{ build.quantity }} {% include "build_status.html" with build=build %} {% if completed %} diff --git a/InvenTree/build/templates/build/complete.html b/InvenTree/build/templates/build/complete.html index 5e9338d656..9f41bee54e 100644 --- a/InvenTree/build/templates/build/complete.html +++ b/InvenTree/build/templates/build/complete.html @@ -1,7 +1,7 @@ {% extends "modal_form.html" %} {% block pre_form_content %} -Build: {{ build.title }} - {{ build.quantity }} x {{ build.part.name }} +Build: {{ build.title }} - {{ build.quantity }} x {{ build.part.full_name }}
Are you sure you want to mark this build as complete?
@@ -24,7 +24,7 @@ The following items will be removed from stock: - {{ item.stock_item.part.name }}
+ {{ item.stock_item.part.full_name }}
{{ item.stock_item.part.description }} {{ item.quantity }} @@ -42,7 +42,7 @@ The following items will be created: - {{ build.quantity }} x {{ build.part.name }} + {{ build.quantity }} x {{ build.part.full_name }}
{% endblock %} \ No newline at end of file diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html index b4de8787b1..a46a146d77 100644 --- a/InvenTree/build/templates/build/detail.html +++ b/InvenTree/build/templates/build/detail.html @@ -11,7 +11,7 @@ InvenTree | Build - {{ build }}

Build Details

{{ build.title }}{% include "build_status.html" with build=build %}

-

Building {{ build.quantity }} × {{ build.part.name }}

+

Building {{ build.quantity }} × {{ build.part.full_name }}

@@ -40,7 +40,7 @@ InvenTree | Build - {{ build }} Title{{ build.title }} - Part{{ build.part.name }} + Part{{ build.part.full_name }} Quantity{{ build.quantity }} @@ -109,7 +109,7 @@ InvenTree | Build - {{ build }} {% for item in build.required_parts %} - {{ item.part.name }} + {{ item.part.full_name }} {{ item.quantity }} {{ item.part.total_stock }} {{ item.allocated }} diff --git a/InvenTree/company/templates/company/delete.html b/InvenTree/company/templates/company/delete.html index 3b029b6df6..1069d790e3 100644 --- a/InvenTree/company/templates/company/delete.html +++ b/InvenTree/company/templates/company/delete.html @@ -7,7 +7,7 @@ Are you sure you want to delete company '{{ company.name }}'? If this supplier is deleted, these supplier part entries will also be deleted.

{% endif %} diff --git a/InvenTree/company/templates/company/detail_part.html b/InvenTree/company/templates/company/detail_part.html index 24982c021f..5d01f1b10e 100644 --- a/InvenTree/company/templates/company/detail_part.html +++ b/InvenTree/company/templates/company/detail_part.html @@ -51,7 +51,7 @@ }, { sortable: true, - field: 'part_detail.name', + field: 'part_detail.full_name', title: 'Part', formatter: function(value, row, index, field) { return imageHoverIcon(row.part_detail.image_url) + renderLink(value, '/part/' + row.part + '/suppliers/'); @@ -74,7 +74,18 @@ sortable: true, field: 'MPN', title: 'MPN', - } + }, + { + field: 'URL', + title: 'URL', + formatter: function(value, row, index, field) { + if (value) { + return renderLink(value, value); + } else { + return ''; + } + } + }, ], url: "{% url 'api-part-supplier-list' %}" }); diff --git a/InvenTree/company/templates/company/partdetail.html b/InvenTree/company/templates/company/partdetail.html index 736b73256d..2924f05453 100644 --- a/InvenTree/company/templates/company/partdetail.html +++ b/InvenTree/company/templates/company/partdetail.html @@ -37,7 +37,7 @@ InvenTree | {{ company.name }} - Parts Internal Part {% if part.part %} - {{ part.part.name }} + {{ part.part.full_name }} {% endif %} diff --git a/InvenTree/part/admin.py b/InvenTree/part/admin.py index c377c27989..18e20cb9ee 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -9,7 +9,7 @@ from .models import BomItem class PartAdmin(ImportExportModelAdmin): - list_display = ('long_name', 'IPN', 'description', 'total_stock', 'category') + list_display = ('full_name', 'description', 'total_stock', 'category') class PartCategoryAdmin(ImportExportModelAdmin): diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 9678819e41..2256f28210 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -24,6 +24,21 @@ class PartImageForm(HelperForm): ] +class BomValidateForm(HelperForm): + """ Simple confirmation form for BOM validation. + User is presented with a single checkbox input, + 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') + + class Meta: + model = Part + fields = [ + 'validate' + ] + + class BomExportForm(HelperForm): # TODO - Define these choices somewhere else, and import them here diff --git a/InvenTree/part/migrations/0022_auto_20190512_1246.py b/InvenTree/part/migrations/0022_auto_20190512_1246.py new file mode 100644 index 0000000000..40f4d0dd4c --- /dev/null +++ b/InvenTree/part/migrations/0022_auto_20190512_1246.py @@ -0,0 +1,31 @@ +# Generated by Django 2.2 on 2019-05-12 02:46 + +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), + ('part', '0021_auto_20190510_2220'), + ] + + operations = [ + migrations.AddField( + 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), + ), + migrations.AddField( + model_name='part', + name='bom_checked_date', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='part', + name='bom_checksum', + field=models.CharField(blank=True, help_text='Stored BOM checksum', max_length=128), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 562fe27f2b..b781287930 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -16,7 +16,7 @@ from django.core.exceptions import ValidationError from django.urls import reverse from django.conf import settings -from django.db import models +from django.db import models, transaction from django.core.validators import MinValueValidator from django.contrib.staticfiles.templatetags.staticfiles import static @@ -24,7 +24,9 @@ from django.contrib.auth.models import User from django.db.models.signals import pre_delete from django.dispatch import receiver +from datetime import datetime from fuzzywuzzy import fuzz +import hashlib from InvenTree import helpers from InvenTree import validators @@ -202,14 +204,30 @@ class Part(models.Model): ] def __str__(self): - return "{n} - {d}".format(n=self.long_name, d=self.description) + return "{n} - {d}".format(n=self.full_name, d=self.description) @property - def long_name(self): - name = self.name + def full_name(self): + """ Format a 'full name' for this Part. + + - IPN (if not null) + - Part name + - Part variant (if not null) + + Elements are joined by the | character + """ + + elements = [] + + if self.IPN: + elements.append(self.IPN) + + elements.append(self.name) + if self.variant: - name += " | " + self.variant - return name + elements.append(self.variant) + + return ' | '.join(elements) def get_absolute_url(self): """ Return the web URL for viewing this part """ @@ -284,23 +302,23 @@ class Part(models.Model): consumable = models.BooleanField(default=True, help_text='Can this part be used to build other parts?') - # Is this part "trackable"? - # Trackable parts can have unique instances - # which are assigned serial numbers (or batch numbers) - # and can have their movements tracked trackable = models.BooleanField(default=False, help_text='Does this part have tracking for unique items?') - # Is this part "purchaseable"? purchaseable = models.BooleanField(default=True, help_text='Can this part be purchased from external suppliers?') - # Can this part be sold to customers? salable = models.BooleanField(default=False, help_text="Can this part be sold to customers?") - # Is this part active? active = models.BooleanField(default=True, help_text='Is this part active?') notes = models.TextField(blank=True) + bom_checksum = models.CharField(max_length=128, blank=True, help_text='Stored BOM checksum') + + bom_checked_by = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, + related_name='boms_checked') + + bom_checked_date = models.DateField(blank=True, null=True) + def format_barcode(self): """ Return a JSON string for formatting a barcode for this Part object """ @@ -451,13 +469,59 @@ class Part(models.Model): @property def bom_count(self): + """ Return the number of items contained in the BOM for this part """ return self.bom_items.count() @property def used_in_count(self): + """ Return the number of part BOMs that this part appears in """ return self.used_in.count() + def get_bom_hash(self): + """ Return a checksum hash for the BOM for this part. + Used to determine if the BOM has changed (and needs to be signed off!) + + For hash is calculated from the following fields of each BOM item: + + - Part.full_name (if the part name changes, the BOM checksum is invalidated) + - quantity + - Note field + + returns a string representation of a hash object which can be compared with a stored value + """ + + hash = hashlib.md5('bom seed'.encode()) + + for item in self.bom_items.all(): + hash.update(str(item.sub_part.full_name).encode()) + hash.update(str(item.quantity).encode()) + hash.update(str(item.note).encode()) + + return str(hash.digest()) + + @property + def is_bom_valid(self): + """ Check if the BOM is 'valid' - if the calculated checksum matches the stored value + """ + + return self.get_bom_hash() == self.bom_checksum + + @transaction.atomic + def validate_bom(self, user): + """ Validate the BOM (mark the BOM as validated by the given User. + + - Calculates and stores the hash for the BOM + - Saves the current date and the checking user + """ + + self.bom_checksum = self.get_bom_hash() + self.bom_checked_by = user + self.bom_checked_date = datetime.now().date() + + self.save() + def required_parts(self): + """ Return a list of parts required to make this part (list of BOM items) """ parts = [] for bom in self.bom_items.all(): parts.append(bom.sub_part) @@ -465,7 +529,7 @@ class Part(models.Model): @property def supplier_count(self): - # Return the number of supplier parts available for this part + """ Return the number of supplier parts available for this part """ return self.supplier_parts.count() def export_bom(self, **kwargs): @@ -480,7 +544,7 @@ class Part(models.Model): for it in self.bom_items.all(): line = [] - line.append(it.sub_part.name) + line.append(it.sub_part.full_name) line.append(it.sub_part.description) line.append(it.quantity) line.append(it.note) @@ -611,8 +675,8 @@ class BomItem(models.Model): def __str__(self): return "{n} x {child} to make {parent}".format( - parent=self.part.name, - child=self.sub_part.name, + parent=self.part.full_name, + child=self.sub_part.full_name, n=self.quantity) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 8cc56daabb..957bfa5951 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -34,14 +34,13 @@ class PartBriefSerializer(serializers.ModelSerializer): url = serializers.CharField(source='get_absolute_url', read_only=True) image_url = serializers.CharField(source='get_image_url', read_only=True) - + class Meta: model = Part fields = [ 'pk', 'url', - 'name', - 'variant', + 'full_name', 'description', 'available_stock', 'image_url', @@ -63,6 +62,7 @@ class PartSerializer(serializers.ModelSerializer): fields = [ 'pk', 'url', # Link to the part detail page + 'full_name', 'name', 'variant', 'image_url', @@ -86,7 +86,7 @@ class PartSerializer(serializers.ModelSerializer): class PartStarSerializer(InvenTreeModelSerializer): """ Serializer for a PartStar object """ - partname = serializers.CharField(source='part.name', read_only=True) + partname = serializers.CharField(source='part.full_name', read_only=True) username = serializers.CharField(source='user.username', read_only=True) class Meta: @@ -145,6 +145,7 @@ class SupplierPartSerializer(serializers.ModelSerializer): 'SKU', 'manufacturer', 'MPN', + 'URL', ] diff --git a/InvenTree/part/templates/part/allocation.html b/InvenTree/part/templates/part/allocation.html index e090855d40..807251a0d1 100644 --- a/InvenTree/part/templates/part/allocation.html +++ b/InvenTree/part/templates/part/allocation.html @@ -17,7 +17,7 @@ {% for allocation in part.build_allocation %} {{ allocation.build.title }} - {{ allocation.build.quantity }} × {{ allocation.build.part.name }} + {{ allocation.build.quantity }} × {{ allocation.build.part.full_name }} {{ allocation.quantity }} {% include "build_status.html" with build=allocation.build %} diff --git a/InvenTree/part/templates/part/bom-delete.html b/InvenTree/part/templates/part/bom-delete.html index d0f36d9b71..492a6eeeda 100644 --- a/InvenTree/part/templates/part/bom-delete.html +++ b/InvenTree/part/templates/part/bom-delete.html @@ -4,6 +4,6 @@ Deleting this entry will remove the BOM row from the following part: diff --git a/InvenTree/part/templates/part/bom-detail.html b/InvenTree/part/templates/part/bom-detail.html index 032cf03123..2c4fb7e09d 100644 --- a/InvenTree/part/templates/part/bom-detail.html +++ b/InvenTree/part/templates/part/bom-detail.html @@ -4,8 +4,8 @@

BOM Item

- - + +
Parent{{ item.part.name }}
Child{{ item.sub_part.name }}
Parent{{ item.part.full_name }}
Child{{ item.sub_part.full_name }}
Quantity{{ item.quantity }}
diff --git a/InvenTree/part/templates/part/bom.html b/InvenTree/part/templates/part/bom.html index c178ef455c..295fed23d6 100644 --- a/InvenTree/part/templates/part/bom.html +++ b/InvenTree/part/templates/part/bom.html @@ -11,6 +11,21 @@

Bill of Materials

+{% if part.bom_checked_date %} +{% if part.is_bom_valid %} +
+{% else %} +
+ The BOM for {{ part.full_name }} has changed, and must be validated.
+{% endif %} + The BOM for {{ part.full_name }} was last checked by {{ part.bom_checked_by }} on {{ part.bom_checked_date }} +
+{% else %} +
+ The BOM for {{ part.full_name }} has not been validated. +
+{% endif %} +
{% if editing_enabled %}
@@ -24,6 +39,9 @@ @@ -72,6 +90,15 @@ {% else %} + $("#validate-bom").click(function() { + launchModalForm( + "{% url 'bom-validate' part.id %}", + { + reload: true, + } + ); + }); + $("#edit-bom").click(function () { location.href = "{% url 'part-bom' part.id %}?edit=True"; }); diff --git a/InvenTree/part/templates/part/bom_validate.html b/InvenTree/part/templates/part/bom_validate.html new file mode 100644 index 0000000000..f2c159349f --- /dev/null +++ b/InvenTree/part/templates/part/bom_validate.html @@ -0,0 +1,5 @@ +{% extends "modal_form.html" %} + +{% block pre_form_content %} +Confirm that the Bill of Materials (BOM) is valid for:
{{ part.full_name }} +{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/category_delete.html b/InvenTree/part/templates/part/category_delete.html index c603637765..298499ab1a 100644 --- a/InvenTree/part/templates/part/category_delete.html +++ b/InvenTree/part/templates/part/category_delete.html @@ -27,7 +27,7 @@ the top level 'Parts' category.

    {% for part in category.parts.all %} -
  • {{ part.long_name }} - {{ part.description }}
  • +
  • {{ part.full_name }} - {{ part.description }}
  • {% endfor %}
{% endif %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/create_part.html b/InvenTree/part/templates/part/create_part.html index 30bbf099f1..4f1fcfb03a 100644 --- a/InvenTree/part/templates/part/create_part.html +++ b/InvenTree/part/templates/part/create_part.html @@ -10,7 +10,7 @@
    {% for match in matches %}
  • - {{ match.part.name }} - {{ match.part.description }} ({{ match.ratio }}%) + {{ match.part.full_name }} - {{ match.part.description }} ({{ match.ratio }}%)
  • {% endfor %}
diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index 3242316136..ddb0523a83 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -16,7 +16,6 @@ {% if part.active %}
  • Duplicate
  • Edit
  • -
  • Stocktake

  • Deactivate
  • {% else %} @@ -34,7 +33,7 @@ - + @@ -46,6 +45,12 @@ {% endif %} + {% if part.URL %} + + + + + {% endif %}
    Part name{{ part.long_name }}{{ part.full_name }}
    Description{{ part.IPN }}
    URL{{ part.URL }}
    Category @@ -147,7 +152,7 @@ $('#activate-part').click(function() { showQuestionDialog( 'Activate Part?', - 'Are you sure you wish to reactivate {{ part.long_name }}?', + 'Are you sure you wish to reactivate {{ part.full_name }}?', { accept_text: 'Activate', accept: function() { @@ -169,7 +174,7 @@ $('#deactivate-part').click(function() { showQuestionDialog( 'Deactivate Part?', - `Are you sure you wish to deactivate {{ part.long_name }}?
    + `Are you sure you wish to deactivate {{ part.full_name }}?
    `, { accept_text: 'Deactivate', @@ -198,16 +203,4 @@ }); }); - $('#stocktake-part').click(function() { - adjustStock({ - action: 'stocktake', - query: { - part: {{ part.id }}, - }, - success: function() { - location.reload(); - } - }); - }); - {% endblock %} diff --git a/InvenTree/part/templates/part/part_app_base.html b/InvenTree/part/templates/part/part_app_base.html index ed3ecfb1b9..95c3f26c28 100644 --- a/InvenTree/part/templates/part/part_app_base.html +++ b/InvenTree/part/templates/part/part_app_base.html @@ -4,7 +4,7 @@ {% block page_title %} {% if part %} -InvenTree | Part - {{ part.long_name }} +InvenTree | Part - {{ part.full_name }} {% elif category %} InvenTree | Part Category - {{ category }} {% else %} diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index d13495bc52..ea3bbc7617 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -7,7 +7,7 @@
    {% if part.active == False %}
    - This part ({{ part.long_name }}) is not active: + This part ({{ part.full_name }}) is not active:
    {% endif %}
    @@ -24,7 +24,7 @@

    - {{ part.long_name }} + {{ part.full_name }}

    {% if part.variant %}

    Variant: {{ part.variant }}

    diff --git a/InvenTree/part/templates/part/partial_delete.html b/InvenTree/part/templates/part/partial_delete.html index e30be8444a..6f2509996b 100644 --- a/InvenTree/part/templates/part/partial_delete.html +++ b/InvenTree/part/templates/part/partial_delete.html @@ -1,10 +1,10 @@ -Are you sure you want to delete part '{{ part.long_name }}'? +Are you sure you want to delete part '{{ part.full_name }}'? {% if part.used_in_count %}

    This part is used in BOMs for {{ part.used_in_count }} other parts. If you delete this part, the BOMs for the following parts will be updated:

      {% for child in part.used_in.all %} -
    • {{ child.part.name }} - {{ child.part.description }}
    • +
    • {{ child.part.full_name }} - {{ child.part.description }}
    • {% endfor %}

      {% endif %} @@ -30,5 +30,5 @@ Are you sure you want to delete part '{{ part.long_name }}'? {% endif %} {% if part.serials.all|length > 0 %} -

      There are {{ part.serials.all|length }} unique parts tracked for '{{ part.long_name }}'. Deleting this part will permanently remove this tracking information.

      +

      There are {{ part.serials.all|length }} unique parts tracked for '{{ part.full_name }}'. Deleting this part will permanently remove this tracking information.

      {% endif %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/supplier.html b/InvenTree/part/templates/part/supplier.html index 5112dad913..041d1fd8c7 100644 --- a/InvenTree/part/templates/part/supplier.html +++ b/InvenTree/part/templates/part/supplier.html @@ -48,7 +48,7 @@ $("#supplier-table").bootstrapTable({ sortable: true, search: true, - formatNoMatches: function() { return "No supplier parts available for {{ part.long_name }}"; }, + formatNoMatches: function() { return "No supplier parts available for {{ part.full_name }}"; }, queryParams: function(p) { return { part: {{ part.id }} diff --git a/InvenTree/part/templates/part/tabs.html b/InvenTree/part/templates/part/tabs.html index d8e84a6f09..ff2cbbf4a0 100644 --- a/InvenTree/part/templates/part/tabs.html +++ b/InvenTree/part/templates/part/tabs.html @@ -12,7 +12,7 @@ {% endif %} {% if part.buildable %} - BOM{{ part.bom_count }} + BOM{{ part.bom_count }} Build{{ part.active_builds|length }} {% endif %} diff --git a/InvenTree/part/templates/part/track.html b/InvenTree/part/templates/part/track.html index 05f9d366d7..9196715437 100644 --- a/InvenTree/part/templates/part/track.html +++ b/InvenTree/part/templates/part/track.html @@ -4,7 +4,7 @@ {% include 'part/tabs.html' with tab='track' %} -Part tracking for {{ part.long_name }} +Part tracking for {{ part.full_name }} diff --git a/InvenTree/part/templates/part/used_in.html b/InvenTree/part/templates/part/used_in.html index f7272dfea6..9210bc446c 100644 --- a/InvenTree/part/templates/part/used_in.html +++ b/InvenTree/part/templates/part/used_in.html @@ -27,7 +27,7 @@ $("#used-table").bootstrapTable({ sortable: true, search: true, - formatNoMatches: function() { return "{{ part.long_name }} is not used to make any other parts"; }, + formatNoMatches: function() { return "{{ part.full_name }} is not used to make any other parts"; }, queryParams: function(p) { return { sub_part: {{ part.id }} @@ -43,7 +43,7 @@ field: 'part_detail', title: 'Part', formatter: function(value, row, index, field) { - return imageHoverIcon(row.part_detail.image_url) + renderLink(value.name, value.url + 'bom/'); + return imageHoverIcon(row.part_detail.image_url) + renderLink(value.full_name, value.url + 'bom/'); } }, { diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index 64b6517489..fb49af8a9b 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -35,6 +35,7 @@ part_detail_urls = [ url(r'^edit/?', views.PartEdit.as_view(), name='part-edit'), url(r'^delete/?', views.PartDelete.as_view(), name='part-delete'), url(r'^bom-export/?', views.BomDownload.as_view(), name='bom-export'), + url(r'^validate-bom/', views.BomValidate.as_view(), name='bom-validate'), url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'), url(r'^attachments/?', views.PartDetail.as_view(template_name='part/attachments.html'), name='part-attachments'), diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 890cb8a519..135e3da144 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -331,12 +331,50 @@ class PartEdit(AjaxUpdateView): return form +class BomValidate(AjaxUpdateView): + """ Modal form view for validating a part BOM """ + + model = Part + ajax_form_title = "Validate BOM" + ajax_template_name = 'part/bom_validate.html' + context_object_name = 'part' + form_class = part_forms.BomValidateForm + + def get_context(self): + return { + 'part': self.get_object(), + } + + def get(self, request, *args, **kwargs): + + form = self.get_form() + + return self.renderJsonResponse(request, form, context=self.get_context()) + + def post(self, request, *args, **kwargs): + + form = self.get_form() + part = self.get_object() + + confirmed = str2bool(request.POST.get('validate', False)) + + if confirmed: + part.validate_bom(request.user) + else: + form.errors['validate'] = ['Confirm that the BOM is valid'] + + data = { + 'form_valid': confirmed + } + + return self.renderJsonResponse(request, form, data, context=self.get_context()) + + class BomExport(AjaxView): model = Part ajax_form_title = 'Export BOM' ajax_template_name = 'part/bom_export.html' - context_object_name = 'part' form_class = part_forms.BomExportForm def get_object(self): @@ -345,17 +383,6 @@ class BomExport(AjaxView): def get(self, request, *args, **kwargs): form = self.form_class() - """ - part = self.get_object() - - context = { - 'part': part - } - - if request.is_ajax(): - passs - """ - return self.renderJsonResponse(request, form) def post(self, request, *args, **kwargs): diff --git a/InvenTree/static/css/inventree.css b/InvenTree/static/css/inventree.css index 9ce6f52400..7761addc55 100644 --- a/InvenTree/static/css/inventree.css +++ b/InvenTree/static/css/inventree.css @@ -14,6 +14,7 @@ color: #ffcc00; } + /* CSS overrides for treeview */ .expand-icon { font-size: 11px; @@ -97,6 +98,10 @@ margin-left: 10px; } +.badge-alert { + background-color: #f33; +} + .part-thumb { width: 200px; height: 200px; @@ -219,6 +224,10 @@ pointer-events: all; } +.alert-block { + display: block; +} + .btn { margin-left: 2px; margin-right: 2px; diff --git a/InvenTree/static/script/inventree/bom.js b/InvenTree/static/script/inventree/bom.js index 762c6b1d6b..6e7a33775a 100644 --- a/InvenTree/static/script/inventree/bom.js +++ b/InvenTree/static/script/inventree/bom.js @@ -93,7 +93,7 @@ function loadBomTable(table, options) { title: 'Part', sortable: true, formatter: function(value, row, index, field) { - return imageHoverIcon(value.image_url) + renderLink(value.name, value.url); + return imageHoverIcon(value.image_url) + renderLink(value.full_name, value.url); } } ); diff --git a/InvenTree/static/script/inventree/part.js b/InvenTree/static/script/inventree/part.js index 82757b47bc..c66fe405b2 100644 --- a/InvenTree/static/script/inventree/part.js +++ b/InvenTree/static/script/inventree/part.js @@ -123,11 +123,7 @@ function loadPartTable(table, url, options={}) { title: 'Part', sortable: true, formatter: function(value, row, index, field) { - var name = row.name; - - if (row.variant) { - name = name + " | " + row.variant; - } + var name = row.full_name; var display = imageHoverIcon(row.image_url) + renderLink(name, row.url); if (!row.active) { diff --git a/InvenTree/static/script/inventree/stock.js b/InvenTree/static/script/inventree/stock.js index ddb20ad1f1..4f32c3f753 100644 --- a/InvenTree/static/script/inventree/stock.js +++ b/InvenTree/static/script/inventree/stock.js @@ -393,7 +393,7 @@ function loadStockTable(table, options) { visible: false, }, { - field: 'part.name', + field: 'part.full_name', title: 'Part', sortable: true, formatter: function(value, row, index, field) { diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index f24516bfcb..09b9cafff4 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -180,7 +180,7 @@ class StockItem(models.Model): reverse('api-stock-detail', kwargs={'pk': self.id}), { 'part_id': self.part.id, - 'part_name': self.part.name + 'part_name': self.part.full_name } ) @@ -464,7 +464,7 @@ class StockItem(models.Model): def __str__(self): s = '{n} x {part}'.format( n=self.quantity, - part=self.part.name) + part=self.part.full_name) if self.location: s += ' @ {loc}'.format(loc=self.location.name) diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index fe8462b16c..a0461ff409 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -32,7 +32,7 @@ class StockItemSerializerBrief(serializers.ModelSerializer): """ Brief serializers for a StockItem """ location_name = serializers.CharField(source='location', read_only=True) - part_name = serializers.CharField(source='part.name', read_only=True) + part_name = serializers.CharField(source='part.full_name', read_only=True) class Meta: model = StockItem diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index daea4ddf24..8bfe5c8e4e 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -5,7 +5,7 @@

      Stock Item Details

      -

      {{ item.quantity }} × {{ item.part.name }}

      +

      {{ item.quantity }} × {{ item.part.full_name }}

      {% include "qr_button.html" %} @@ -39,7 +39,7 @@
      - + {% if item.belongs_to %} diff --git a/InvenTree/stock/templates/stock/item_delete.html b/InvenTree/stock/templates/stock/item_delete.html index ff22da1d10..9f3f863d75 100644 --- a/InvenTree/stock/templates/stock/item_delete.html +++ b/InvenTree/stock/templates/stock/item_delete.html @@ -2,4 +2,4 @@ Are you sure you want to delete this stock item?
      -This will remove {{ item.quantity }} units of {{ item.part.name }} from stock. +This will remove {{ item.quantity }} units of {{ item.part.full_name }} from stock. diff --git a/InvenTree/stock/templates/stock/location_delete.html b/InvenTree/stock/templates/stock/location_delete.html index 6c3ee981e5..ecb60ae1f1 100644 --- a/InvenTree/stock/templates/stock/location_delete.html +++ b/InvenTree/stock/templates/stock/location_delete.html @@ -30,7 +30,7 @@ If this location is deleted, these items will be moved to the top level 'Stock'
        {% for item in location.stock_items.all %} -
      • {{ item.part.name }} - {{ item.part.description }}{{ item.quantity }}
      • +
      • {{ item.part.full_name }} - {{ item.part.description }}{{ item.quantity }}
      • {% endfor %}
      {% endif %} diff --git a/InvenTree/templates/required_part_table.html b/InvenTree/templates/required_part_table.html index 5d362e8f77..5a8c7e5734 100644 --- a/InvenTree/templates/required_part_table.html +++ b/InvenTree/templates/required_part_table.html @@ -8,7 +8,7 @@ {% for part in parts %} - +
      Part{{ item.part.name }}{{ item.part.full_name }}
      {{ part.long_name }}{{ part.full_name }} {{ part.description }} {{ part.total_stock }} {{ part.allocation_count }}