From 1d36ea3e2e644505b786ebbb4015d866ae56f2f8 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 12 May 2019 12:16:04 +1000 Subject: [PATCH 01/11] Add a 'full_name' field for Part - Combines IPN | Name | Variant - Use this to display in most views --- InvenTree/build/models.py | 4 +-- InvenTree/build/serializers.py | 2 +- InvenTree/build/templates/build/allocate.html | 4 +-- .../templates/build/allocation_item.html | 2 +- .../build/templates/build/auto_allocate.html | 4 +-- .../build/templates/build/build_list.html | 2 +- InvenTree/build/templates/build/complete.html | 6 ++-- InvenTree/build/templates/build/detail.html | 6 ++-- .../company/templates/company/delete.html | 2 +- .../templates/company/detail_part.html | 2 +- .../company/templates/company/partdetail.html | 2 +- InvenTree/part/admin.py | 2 +- InvenTree/part/models.py | 30 ++++++++++++++----- InvenTree/part/serializers.py | 8 ++--- InvenTree/part/templates/part/allocation.html | 2 +- InvenTree/part/templates/part/bom-delete.html | 2 +- InvenTree/part/templates/part/bom-detail.html | 4 +-- .../part/templates/part/category_delete.html | 2 +- .../part/templates/part/create_part.html | 2 +- InvenTree/part/templates/part/detail.html | 25 ++++++---------- .../part/templates/part/part_app_base.html | 2 +- InvenTree/part/templates/part/part_base.html | 4 +-- .../part/templates/part/partial_delete.html | 6 ++-- InvenTree/part/templates/part/supplier.html | 2 +- InvenTree/part/templates/part/track.html | 2 +- InvenTree/part/templates/part/used_in.html | 4 +-- InvenTree/static/script/inventree/bom.js | 2 +- InvenTree/static/script/inventree/part.js | 6 +--- InvenTree/static/script/inventree/stock.js | 2 +- InvenTree/stock/models.py | 4 +-- InvenTree/stock/serializers.py | 2 +- InvenTree/stock/templates/stock/item.html | 4 +-- .../stock/templates/stock/item_delete.html | 2 +- .../templates/stock/location_delete.html | 2 +- InvenTree/templates/required_part_table.html | 2 +- 35 files changed, 81 insertions(+), 78 deletions(-) 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.

    {% for part in company.parts.all %} -
  • {{ part.SKU }} - {{ part.part.name }}
  • +
  • {{ part.SKU }} - {{ part.part.full_name }}
  • {% endfor %}
{% endif %} diff --git a/InvenTree/company/templates/company/detail_part.html b/InvenTree/company/templates/company/detail_part.html index 24982c021f..63234b9eff 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/'); 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/models.py b/InvenTree/part/models.py index 562fe27f2b..ac322887e2 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -202,14 +202,28 @@ 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 = [] + + 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 """ @@ -480,7 +494,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 +625,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..d4cb5ac8aa 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: 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:
  • - {{ item.part.name }} - {{ item.part.description }} + {{ item.part.full_name }} - {{ item.part.description }}
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/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.

{% 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 @@ 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/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/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 %} - + From d5dbc1b0720c4b66c3d78fecd762fdc91a9d02df Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 12 May 2019 12:20:15 +1000 Subject: [PATCH 02/11] Display supplier part URL in table --- .../company/templates/company/detail_part.html | 13 ++++++++++++- InvenTree/part/serializers.py | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/InvenTree/company/templates/company/detail_part.html b/InvenTree/company/templates/company/detail_part.html index 63234b9eff..5d01f1b10e 100644 --- a/InvenTree/company/templates/company/detail_part.html +++ b/InvenTree/company/templates/company/detail_part.html @@ -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/part/serializers.py b/InvenTree/part/serializers.py index d4cb5ac8aa..957bfa5951 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -145,6 +145,7 @@ class SupplierPartSerializer(serializers.ModelSerializer): 'SKU', 'manufacturer', 'MPN', + 'URL', ] From 82fb43e1e3b79881a7cb04cd09997d36f6f362ea Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 12 May 2019 12:29:16 +1000 Subject: [PATCH 03/11] PEP --- InvenTree/part/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index ac322887e2..06ec2f1955 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -206,11 +206,13 @@ class Part(models.Model): @property def full_name(self): - """ Format a 'full name' for this Part. + """ 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 = [] From 4c3032e2f09499b0c34a61c622f40b1420246a4f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 12 May 2019 12:42:06 +1000 Subject: [PATCH 04/11] Add function to calculate BOM hash - Uses hashlib.md5 --- InvenTree/part/models.py | 24 +++++++++++++++++++++++ InvenTree/part/templates/part/detail.html | 4 ++++ 2 files changed, 28 insertions(+) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 06ec2f1955..3c7cc22a32 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -25,6 +25,7 @@ from django.db.models.signals import pre_delete from django.dispatch import receiver from fuzzywuzzy import fuzz +import hashlib from InvenTree import helpers from InvenTree import validators @@ -473,6 +474,29 @@ class Part(models.Model): def used_in_count(self): return self.used_in.count() + @property + 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.notes).encode()) + + return str(hash.digest()) + def required_parts(self): parts = [] for bom in self.bom_items.all(): diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index ddb0523a83..42c7af0bfe 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -51,6 +51,10 @@ {% endif %} + + + + {% endif %} - - - -
      Part{{ item.part.name }}{{ item.part.full_name }}
      {{ part.long_name }}{{ part.full_name }} {{ part.description }} {{ part.total_stock }} {{ part.allocation_count }}{{ part.URL }}
      Hash{{ part.get_bom_hash }}
      Category From 2431ba2a04c50409f2cb7c9e0c8ae322add63ed2 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 12 May 2019 12:47:28 +1000 Subject: [PATCH 05/11] Add new fields to Part model - bom_checksum (stores checksum calculated when the BOM was checked) - bom_checked_by (User who checked the BOM) - bom_checked_date (When the BOM was last checked) --- .../migrations/0022_auto_20190512_1246.py | 31 +++++++++++++++++++ InvenTree/part/models.py | 16 +++++----- 2 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 InvenTree/part/migrations/0022_auto_20190512_1246.py 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 3c7cc22a32..12bcca2f04 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -301,23 +301,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 """ @@ -493,7 +493,7 @@ class Part(models.Model): 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.notes).encode()) + hash.update(str(item.note).encode()) return str(hash.digest()) From 985986a844014f844639b5a640a667f073e0bc56 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 12 May 2019 12:53:56 +1000 Subject: [PATCH 06/11] New functions for Part model - is_bom_valid() - Tests if bom checksums match - check_bom() function to mark the BOM as valid --- InvenTree/part/models.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 12bcca2f04..5f844228d6 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 @@ -474,7 +474,6 @@ class Part(models.Model): def used_in_count(self): return self.used_in.count() - @property 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!) @@ -497,7 +496,29 @@ class Part(models.Model): 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 check_bom(self, user): + """ Check 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 = 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) @@ -505,7 +526,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): From 06deccca1c653b16c9e2c0dc5cff9bb010f9229e Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 12 May 2019 13:01:41 +1000 Subject: [PATCH 07/11] Rename check_bom to validate_bom --- InvenTree/part/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 5f844228d6..434c91be1a 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -504,7 +504,7 @@ class Part(models.Model): return self.get_bom_hash() == self.bom_checksum @transaction.atomic - def check_bom(self, user): + def validate_bom(self, user): """ Check the BOM (mark the BOM as validated by the given User. - Calculates and stores the hash for the BOM From 9149619f38264c5fbc00898ae8fdec41e67138da Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 12 May 2019 13:12:04 +1000 Subject: [PATCH 08/11] Make BOM tab badge red if the BOM is not validated --- InvenTree/part/models.py | 2 +- InvenTree/part/templates/part/tabs.html | 2 +- InvenTree/static/css/inventree.css | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 434c91be1a..22b0073e6c 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -505,7 +505,7 @@ class Part(models.Model): @transaction.atomic def validate_bom(self, user): - """ Check the BOM (mark the BOM as validated by the given 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 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/static/css/inventree.css b/InvenTree/static/css/inventree.css index 9ce6f52400..49e2d636c7 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; From d17e36b9f946c777898b33efbb45d4b821f15e8b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 12 May 2019 13:24:58 +1000 Subject: [PATCH 09/11] Add BOM checksum info panels to the BOM view --- InvenTree/part/templates/part/bom.html | 15 +++++++++++++++ InvenTree/static/css/inventree.css | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/InvenTree/part/templates/part/bom.html b/InvenTree/part/templates/part/bom.html index c178ef455c..a94569d42c 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 %}
      diff --git a/InvenTree/static/css/inventree.css b/InvenTree/static/css/inventree.css index 49e2d636c7..7761addc55 100644 --- a/InvenTree/static/css/inventree.css +++ b/InvenTree/static/css/inventree.css @@ -224,6 +224,10 @@ pointer-events: all; } +.alert-block { + display: block; +} + .btn { margin-left: 2px; margin-right: 2px; From c7f0d56be44b70d0a1a8f1364a5c5d80f2a224f3 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 12 May 2019 16:09:11 +1000 Subject: [PATCH 10/11] Don't display BOM hash any more! --- InvenTree/part/templates/part/detail.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index 42c7af0bfe..ddb0523a83 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -51,10 +51,6 @@
      {{ part.URL }}
      Hash{{ part.get_bom_hash }}
      Category From e3a9a70678a40cfb3ea7be73c30dd66355e8d524 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 12 May 2019 16:27:50 +1000 Subject: [PATCH 11/11] Add a form/view/etc for BOM validation --- InvenTree/part/forms.py | 15 ++++++ InvenTree/part/models.py | 7 ++- InvenTree/part/templates/part/bom.html | 12 +++++ .../part/templates/part/bom_validate.html | 5 ++ InvenTree/part/urls.py | 1 + InvenTree/part/views.py | 51 ++++++++++++++----- 6 files changed, 77 insertions(+), 14 deletions(-) create mode 100644 InvenTree/part/templates/part/bom_validate.html 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/models.py b/InvenTree/part/models.py index 22b0073e6c..b781287930 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -24,6 +24,7 @@ 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 @@ -468,14 +469,16 @@ 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. + """ 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: @@ -511,7 +514,7 @@ class Part(models.Model): - Saves the current date and the checking user """ - self.bom_checksum = get_bom_hash() + self.bom_checksum = self.get_bom_hash() self.bom_checked_by = user self.bom_checked_date = datetime.now().date() diff --git a/InvenTree/part/templates/part/bom.html b/InvenTree/part/templates/part/bom.html index a94569d42c..295fed23d6 100644 --- a/InvenTree/part/templates/part/bom.html +++ b/InvenTree/part/templates/part/bom.html @@ -39,6 +39,9 @@ @@ -87,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/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):