diff --git a/docs/docs/assets/images/part/part_create_revision.png b/docs/docs/assets/images/part/part_create_revision.png
new file mode 100644
index 0000000000..a3a901d3c6
Binary files /dev/null and b/docs/docs/assets/images/part/part_create_revision.png differ
diff --git a/docs/docs/assets/images/part/part_revision_b.png b/docs/docs/assets/images/part/part_revision_b.png
new file mode 100644
index 0000000000..501d638a04
Binary files /dev/null and b/docs/docs/assets/images/part/part_revision_b.png differ
diff --git a/docs/docs/assets/images/part/part_revision_select.png b/docs/docs/assets/images/part/part_revision_select.png
new file mode 100644
index 0000000000..48da57a3b3
Binary files /dev/null and b/docs/docs/assets/images/part/part_revision_select.png differ
diff --git a/docs/docs/assets/images/part/part_revision_settings.png b/docs/docs/assets/images/part/part_revision_settings.png
new file mode 100644
index 0000000000..31940f53dd
Binary files /dev/null and b/docs/docs/assets/images/part/part_revision_settings.png differ
diff --git a/docs/docs/part/revision.md b/docs/docs/part/revision.md
new file mode 100644
index 0000000000..4d3a9bd79d
--- /dev/null
+++ b/docs/docs/part/revision.md
@@ -0,0 +1,78 @@
+---
+title: Part Revisions
+---
+
+## Part Revisions
+
+When creating a complex part (such as an assembly comprised of other parts), it is often necessary to track changes to the part over time. For example, throughout the lifetime of an assembly, it may be necessary to adjust the bill of materials, or update the design of the part.
+
+Rather than overwrite the existing part data, InvenTree allows you to create a new *revision* of the part. This allows you to track changes to the part over time, and maintain a history of the part design.
+
+Crucially, creating a new *revision* ensures that any related data entries which refer to the original part (such as stock items, build orders, purchase orders, etc) are not affected by the change.
+
+### Revisions are Parts
+
+A *revision* of a part is itself a part. This means that each revision of a part has its own part number, stock items, parameters, bill of materials, etc. The only thing that differentiates a *revision* from any other part is that the *revision* is linked to the original part.
+
+### Revision Fields
+
+Each part has two fields which are used to track the revision of the part:
+
+* **Revision**: The revision number of the part. This is a user-defined field, and can be any string value.
+* **Revision Of**: A reference to the part of which *this* part is a revision. This field is used to keep track of the available revisions for any particular part.
+
+### Revision Restrictions
+
+When creating a new revision of a part, there are some restrictions which must be adhered to:
+
+* **Circular References**: A part cannot be a revision of itself. This would create a circular reference which is not allowed.
+* **Unique Revisions**: A part cannot have two revisions with the same revision number. Each revision (of a given part) must have a unique revision code.
+* **Revisions of Revisions**: A single part can have multiple revisions, but a revision cannot have its own revision. This restriction is in place to prevent overly complex part relationships.
+* **Template Revisions**: A part which is a [template part](./template.md) cannot have revisions. This is because the template part is used to create variants, and allowing revisions of templates would create disallowed relationship states in the database. However, variant parts are allowed to have revisions.
+* **Template References**: A part which is a revision of a variant part must point to the same template as the original part. This is to ensure that the revision is correctly linked to the original part.
+
+## Revision Settings
+
+The following options are available to control the behavior of part revisions.
+
+Note that these options can be changed in the InvenTree settings:
+
+{% with id="part_revision_settings", url="part/part_revision_settings.png", description="Part revision settings" %}
+{% include 'img.html' %}
+{% endwith %}
+
+* **Enable Revisions**: If this setting is enabled, parts can have revisions. If this setting is disabled, parts cannot have revisions.
+* **Assembly Revisions Only**: If this setting is enabled, only assembly parts can have revisions. This is useful if you only want to track revisions of assemblies, and not individual parts.
+
+## Create a Revision
+
+To create a new revision for a given part, navigate to the part detail page, and click on the "Revisions" tab.
+
+Select the "Duplicate Part" action, to create a new copy of the selected part. This will open the "Duplicate Part" form:
+
+{% with id="part_create_revision", url="part/part_create_revision.png", description="Create part revision" %}
+{% include 'img.html' %}
+{% endwith %}
+
+In this form, make the following updates:
+
+1. Set the *Revision Of* field to the original part (the one that you are duplicating)
+2. Set the *Revision* field to a unique revision number for the new part revision
+
+Once these changes (and any other required changes) are made, press *Submit* to create the new part.
+
+Once the form is submitted (without any errors), you will be redirected to the new part revision. Here you can see that it is linked to the original part:
+
+{% with id="part_revision_b", url="part/part_revision_b.png", description="Revision B" %}
+{% include 'img.html' %}
+{% endwith %}
+
+## Revision Navigation
+
+When multiple revisions exist for a particular part, you can navigate between revisions using the *Select Part Revision* drop-down which renders at the top of the part page:
+
+{% with id="part_revision_select", url="part/part_revision_select.png", description="Select part revision" %}
+{% include 'img.html' %}
+{% endwith %}
+
+Note that this revision selector is only visible when multiple revisions exist for the part.
diff --git a/docs/docs/part/views.md b/docs/docs/part/views.md
index f2301d9ea2..c574bccf7e 100644
--- a/docs/docs/part/views.md
+++ b/docs/docs/part/views.md
@@ -28,7 +28,6 @@ Details provides information about the particular part. Parts details can be dis
{% with id="part_overview", url="part/part_overview.png", description="Part details" %}
{% include 'img.html' %}
{% endwith %}
-
A Part is defined in the system by the following parameters:
@@ -38,7 +37,7 @@ A Part is defined in the system by the following parameters:
**Description** - Longer form text field describing the Part
-**Revision** - An optional revision code denoting the particular version for the part. Used when there are multiple revisions of the same master part object.
+**Revision** - An optional revision code denoting the particular version for the part. Used when there are multiple revisions of the same master part object. Read [more about part revisions here](./revision.md).
**Keywords** - Optional few words to describe the part and make the part search more efficient.
@@ -62,7 +61,7 @@ Parts can have multiple defined parameters.
If a part is a *Template Part* then the *Variants* tab will be visible.
-[Read about Part templates](./template.md)
+[Read about Part templates and variants](./template.md)
### Stock
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 14c8ddd832..75974387d7 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -106,6 +106,7 @@ nav:
- Part Views: part/views.md
- Tracking: part/trackable.md
- Parameters: part/parameter.md
+ - Revisions: part/revision.md
- Templates: part/template.md
- Tests: part/test.md
- Pricing: part/pricing.md
diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py
index bdb4330d90..b11316cb69 100644
--- a/src/backend/InvenTree/InvenTree/api_version.py
+++ b/src/backend/InvenTree/InvenTree/api_version.py
@@ -1,12 +1,16 @@
"""InvenTree API version information."""
# InvenTree API version
-INVENTREE_API_VERSION = 219
+INVENTREE_API_VERSION = 220
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
+v220 - 2024-07-11 : https://github.com/inventree/InvenTree/pull/7585
+ - Adds "revision_of" field to Part serializer
+ - Adds new API filters for "revision" status
+
v219 - 2024-07-11 : https://github.com/inventree/InvenTree/pull/7611
- Adds new fields to the BuildItem API endpoints
- Adds new ordering / filtering options to the BuildItem API endpoints
diff --git a/src/backend/InvenTree/build/serializers.py b/src/backend/InvenTree/build/serializers.py
index ef84817123..7c8bb2a0aa 100644
--- a/src/backend/InvenTree/build/serializers.py
+++ b/src/backend/InvenTree/build/serializers.py
@@ -1,5 +1,7 @@
"""JSON serializers for Build API."""
+from decimal import Decimal
+
from django.db import transaction
from django.core.exceptions import ValidationError as DjangoValidationError
from django.utils.translation import gettext_lazy as _
@@ -209,7 +211,7 @@ class BuildOutputQuantitySerializer(BuildOutputSerializer):
quantity = serializers.DecimalField(
max_digits=15,
decimal_places=5,
- min_value=0,
+ min_value=Decimal(0),
required=True,
label=_('Quantity'),
help_text=_('Enter quantity for build output'),
@@ -256,7 +258,7 @@ class BuildOutputCreateSerializer(serializers.Serializer):
quantity = serializers.DecimalField(
max_digits=15,
decimal_places=5,
- min_value=0,
+ min_value=Decimal(0),
required=True,
label=_('Quantity'),
help_text=_('Enter quantity for build output'),
@@ -864,7 +866,7 @@ class BuildAllocationItemSerializer(serializers.Serializer):
quantity = serializers.DecimalField(
max_digits=15,
decimal_places=5,
- min_value=0,
+ min_value=Decimal(0),
required=True
)
diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py
index 9d41d06f39..a55e829cc8 100644
--- a/src/backend/InvenTree/common/models.py
+++ b/src/backend/InvenTree/common/models.py
@@ -1408,6 +1408,12 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'validator': bool,
'default': True,
},
+ 'PART_REVISION_ASSEMBLY_ONLY': {
+ 'name': _('Assembly Revision Only'),
+ 'description': _('Only allow revisions for assembly parts'),
+ 'validator': bool,
+ 'default': False,
+ },
'PART_ALLOW_DELETE_FROM_ASSEMBLY': {
'name': _('Allow Deletion from Assembly'),
'description': _('Allow deletion of parts which are used in an assembly'),
diff --git a/src/backend/InvenTree/company/test_api.py b/src/backend/InvenTree/company/test_api.py
index 65b55089a3..afe04e6653 100644
--- a/src/backend/InvenTree/company/test_api.py
+++ b/src/backend/InvenTree/company/test_api.py
@@ -57,22 +57,20 @@ class CompanyTest(InvenTreeAPITestCase):
def test_company_detail(self):
"""Tests for the Company detail endpoint."""
url = reverse('api-company-detail', kwargs={'pk': self.acme.pk})
- response = self.get(url)
+ response = self.get(url, expected_code=200)
+ self.assertIn('name', response.data.keys())
self.assertEqual(response.data['name'], 'ACME')
# Change the name of the company
# Note we should not have the correct permissions (yet)
data = response.data
- response = self.client.patch(url, data, format='json', expected_code=400)
-
- self.assignRole('company.change')
# Update the name and set the currency to a valid value
data['name'] = 'ACMOO'
data['currency'] = 'NZD'
- response = self.client.patch(url, data, format='json', expected_code=200)
+ response = self.patch(url, data, expected_code=200)
self.assertEqual(response.data['name'], 'ACMOO')
self.assertEqual(response.data['currency'], 'NZD')
diff --git a/src/backend/InvenTree/order/serializers.py b/src/backend/InvenTree/order/serializers.py
index 1226e753ce..be46b497c7 100644
--- a/src/backend/InvenTree/order/serializers.py
+++ b/src/backend/InvenTree/order/serializers.py
@@ -616,7 +616,7 @@ class PurchaseOrderLineItemReceiveSerializer(serializers.Serializer):
)
quantity = serializers.DecimalField(
- max_digits=15, decimal_places=5, min_value=0, required=True
+ max_digits=15, decimal_places=5, min_value=Decimal(0), required=True
)
def validate_quantity(self, quantity):
@@ -1250,7 +1250,7 @@ class SalesOrderShipmentAllocationItemSerializer(serializers.Serializer):
)
quantity = serializers.DecimalField(
- max_digits=15, decimal_places=5, min_value=0, required=True
+ max_digits=15, decimal_places=5, min_value=Decimal(0), required=True
)
def validate_quantity(self, quantity):
diff --git a/src/backend/InvenTree/part/api.py b/src/backend/InvenTree/part/api.py
index 07a8c56793..2d9be8b099 100644
--- a/src/backend/InvenTree/part/api.py
+++ b/src/backend/InvenTree/part/api.py
@@ -911,7 +911,27 @@ class PartFilter(rest_filters.FilterSet):
"""Metaclass options for this filter set."""
model = Part
- fields = []
+ fields = ['revision_of']
+
+ is_revision = rest_filters.BooleanFilter(
+ label=_('Is Revision'), method='filter_is_revision'
+ )
+
+ def filter_is_revision(self, queryset, name, value):
+ """Filter by whether the Part is a revision or not."""
+ if str2bool(value):
+ return queryset.exclude(revision_of=None)
+ return queryset.filter(revision_of=None)
+
+ has_revisions = rest_filters.BooleanFilter(
+ label=_('Has Revisions'), method='filter_has_revisions'
+ )
+
+ def filter_has_revisions(self, queryset, name, value):
+ """Filter by whether the Part has any revisions or not."""
+ if str2bool(value):
+ return queryset.exclude(revision_count=0)
+ return queryset.filter(revision_count=0)
has_units = rest_filters.BooleanFilter(label='Has units', method='filter_has_units')
@@ -1361,6 +1381,8 @@ class PartList(PartMixin, DataExportViewMixin, ListCreateAPI):
'pricing_min',
'pricing_max',
'pricing_updated',
+ 'revision',
+ 'revision_count',
]
ordering_field_aliases = {
diff --git a/src/backend/InvenTree/part/migrations/0126_part_revision_of.py b/src/backend/InvenTree/part/migrations/0126_part_revision_of.py
new file mode 100644
index 0000000000..e5324d60a9
--- /dev/null
+++ b/src/backend/InvenTree/part/migrations/0126_part_revision_of.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.2.12 on 2024-07-07 04:42
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('part', '0125_part_locked'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='part',
+ name='revision_of',
+ field=models.ForeignKey(help_text='Is this part a revision of another part?', null=True, blank=True, on_delete=django.db.models.deletion.SET_NULL, related_name='revisions', to='part.part', verbose_name='Revision Of'),
+ ),
+ ]
diff --git a/src/backend/InvenTree/part/models.py b/src/backend/InvenTree/part/models.py
index af017e188b..730cf5a270 100644
--- a/src/backend/InvenTree/part/models.py
+++ b/src/backend/InvenTree/part/models.py
@@ -662,6 +662,49 @@ class Part(
if match is None:
raise ValidationError(_(f'IPN must match regex pattern {pattern}'))
+ def validate_revision(self):
+ """Check the 'revision' and 'revision_of' fields."""
+ # Part cannot be a revision of itself
+ if self.revision_of:
+ if self.revision_of == self:
+ raise ValidationError({
+ 'revision_of': _('Part cannot be a revision of itself')
+ })
+
+ # Part cannot be a revision of a part which is itself a revision
+ if self.revision_of.revision_of:
+ raise ValidationError({
+ 'revision_of': _(
+ 'Cannot make a revision of a part which is already a revision'
+ )
+ })
+
+ # If this part is a revision, it must have a revision code
+ if not self.revision:
+ raise ValidationError({
+ 'revision': _('Revision code must be specified')
+ })
+
+ if get_global_setting('PART_REVISION_ASSEMBLY_ONLY'):
+ if not self.assembly or not self.revision_of.assembly:
+ raise ValidationError({
+ 'revision_of': _(
+ 'Revisions are only allowed for assembly parts'
+ )
+ })
+
+ # Cannot have a revision of a "template" part
+ if self.revision_of.is_template:
+ raise ValidationError({
+ 'revision_of': _('Cannot make a revision of a template part')
+ })
+
+ # parent part must point to the same template (via variant_of)
+ if self.variant_of != self.revision_of.variant_of:
+ raise ValidationError({
+ 'revision_of': _('Parent part must point to the same template')
+ })
+
def validate_serial_number(
self,
serial: str,
@@ -842,15 +885,24 @@ class Part(
'IPN': _('Duplicate IPN not allowed in part settings')
})
+ if self.revision_of and self.revision:
+ if (
+ Part.objects.exclude(pk=self.pk)
+ .filter(revision_of=self.revision_of, revision=self.revision)
+ .exists()
+ ):
+ raise ValidationError(_('Duplicate part revision already exists.'))
+
# Ensure unique across (Name, revision, IPN) (as specified)
- if (
- Part.objects.exclude(pk=self.pk)
- .filter(name=self.name, revision=self.revision, IPN=self.IPN)
- .exists()
- ):
- raise ValidationError(
- _('Part with this Name, IPN and Revision already exists.')
- )
+ if self.revision or self.IPN:
+ if (
+ Part.objects.exclude(pk=self.pk)
+ .filter(name=self.name, revision=self.revision, IPN=self.IPN)
+ .exists()
+ ):
+ raise ValidationError(
+ _('Part with this Name, IPN and Revision already exists.')
+ )
def clean(self):
"""Perform cleaning operations for the Part model.
@@ -867,6 +919,9 @@ class Part(
'category': _('Parts cannot be assigned to structural part categories!')
})
+ # Check the 'revision' and 'revision_of' fields
+ self.validate_revision()
+
super().clean()
# Strip IPN field
@@ -954,6 +1009,16 @@ class Part(
verbose_name=_('Revision'),
)
+ revision_of = models.ForeignKey(
+ 'part.Part',
+ related_name='revisions',
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ help_text=_('Is this part a revision of another part?'),
+ verbose_name=_('Revision Of'),
+ )
+
link = InvenTreeURLField(
blank=True,
null=True,
diff --git a/src/backend/InvenTree/part/serializers.py b/src/backend/InvenTree/part/serializers.py
index c2aa330339..a5e4f783d6 100644
--- a/src/backend/InvenTree/part/serializers.py
+++ b/src/backend/InvenTree/part/serializers.py
@@ -658,6 +658,8 @@ class PartSerializer(
'pk',
'purchaseable',
'revision',
+ 'revision_of',
+ 'revision_count',
'salable',
'starred',
'thumbnail',
@@ -762,6 +764,9 @@ class PartSerializer(
"""
queryset = queryset.prefetch_related('category', 'default_location')
+ # Annotate with the total number of revisions
+ queryset = queryset.annotate(revision_count=SubqueryCount('revisions'))
+
# Annotate with the total number of stock items
queryset = queryset.annotate(stock_item_count=SubqueryCount('stock_items'))
@@ -883,6 +888,7 @@ class PartSerializer(
required_for_build_orders = serializers.IntegerField(read_only=True)
required_for_sales_orders = serializers.IntegerField(read_only=True)
stock_item_count = serializers.IntegerField(read_only=True, label=_('Stock Items'))
+ revision_count = serializers.IntegerField(read_only=True, label=_('Revisions'))
suppliers = serializers.IntegerField(read_only=True, label=_('Suppliers'))
total_in_stock = serializers.FloatField(read_only=True, label=_('Total Stock'))
external_stock = serializers.FloatField(read_only=True, label=_('External Stock'))
diff --git a/src/backend/InvenTree/part/templates/part/part_base.html b/src/backend/InvenTree/part/templates/part/part_base.html
index 3b6b57542d..835258c8ea 100644
--- a/src/backend/InvenTree/part/templates/part/part_base.html
+++ b/src/backend/InvenTree/part/templates/part/part_base.html
@@ -271,6 +271,15 @@
{% endif %}
{% settings_value "PART_ENABLE_REVISION" as show_revision %}
+ {% if show_revision and part.revision_of %}
+
+ {% endif %}
{% if show_revision and part.revision %}
diff --git a/src/backend/InvenTree/part/test_part.py b/src/backend/InvenTree/part/test_part.py
index 6e85c11cc0..6f0eb5beba 100644
--- a/src/backend/InvenTree/part/test_part.py
+++ b/src/backend/InvenTree/part/test_part.py
@@ -389,6 +389,83 @@ class PartTest(TestCase):
part.delete()
+ def test_revisions(self):
+ """Test the 'revision' and 'revision_of' field."""
+ template = Part.objects.create(
+ name='Template part', description='A template part', is_template=True
+ )
+
+ # Create a new part
+ part = Part.objects.create(
+ name='Master Part',
+ description='Master part (will have revisions)',
+ variant_of=template,
+ )
+
+ self.assertEqual(part.revisions.count(), 0)
+
+ # Try to set as revision of itself
+ with self.assertRaises(ValidationError) as exc:
+ part.revision_of = part
+ part.save()
+
+ self.assertIn('Part cannot be a revision of itself', str(exc.exception))
+
+ part.refresh_from_db()
+
+ rev_a = Part.objects.create(
+ name='Master Part', description='Master part (revision A)'
+ )
+
+ with self.assertRaises(ValidationError) as exc:
+ print('rev a:', rev_a.revision_of, part.revision_of)
+ rev_a.revision_of = part
+ rev_a.save()
+
+ self.assertIn('Revision code must be specified', str(exc.exception))
+
+ with self.assertRaises(ValidationError) as exc:
+ rev_a.revision_of = template
+ rev_a.revision = 'A'
+ rev_a.save()
+
+ self.assertIn('Cannot make a revision of a template part', str(exc.exception))
+
+ with self.assertRaises(ValidationError) as exc:
+ rev_a.revision_of = part
+ rev_a.revision = 'A'
+ rev_a.save()
+
+ self.assertIn('Parent part must point to the same template', str(exc.exception))
+
+ rev_a.variant_of = template
+ rev_a.revision_of = part
+ rev_a.revision = 'A'
+ rev_a.save()
+
+ self.assertEqual(part.revisions.count(), 1)
+
+ rev_b = Part.objects.create(
+ name='Master Part', description='Master part (revision B)'
+ )
+
+ with self.assertRaises(ValidationError) as exc:
+ rev_b.revision_of = rev_a
+ rev_b.revision = 'B'
+ rev_b.save()
+
+ self.assertIn(
+ 'Cannot make a revision of a part which is already a revision',
+ str(exc.exception),
+ )
+
+ rev_b.variant_of = template
+ rev_b.revision_of = part
+ rev_b.revision = 'B'
+ rev_b.save()
+
+ self.assertEqual(part.revisions.count(), 2)
+
class TestTemplateTest(TestCase):
"""Unit test for the TestTemplate class."""
diff --git a/src/backend/InvenTree/stock/serializers.py b/src/backend/InvenTree/stock/serializers.py
index 7560bc2632..fa9e060e72 100644
--- a/src/backend/InvenTree/stock/serializers.py
+++ b/src/backend/InvenTree/stock/serializers.py
@@ -1518,7 +1518,7 @@ class StockAdjustmentItemSerializer(serializers.Serializer):
)
quantity = serializers.DecimalField(
- max_digits=15, decimal_places=5, min_value=0, required=True
+ max_digits=15, decimal_places=5, min_value=Decimal(0), required=True
)
batch = serializers.CharField(
diff --git a/src/backend/InvenTree/templates/InvenTree/settings/part.html b/src/backend/InvenTree/templates/InvenTree/settings/part.html
index dae8368b7f..96aafc3f30 100644
--- a/src/backend/InvenTree/templates/InvenTree/settings/part.html
+++ b/src/backend/InvenTree/templates/InvenTree/settings/part.html
@@ -12,6 +12,7 @@
{% include "InvenTree/settings/setting.html" with key="PART_ENABLE_REVISION" %}
+ {% include "InvenTree/settings/setting.html" with key="PART_REVISION_ASSEMBLY_ONLY" %}
{% include "InvenTree/settings/setting.html" with key="PART_IPN_REGEX" %}
{% include "InvenTree/settings/setting.html" with key="PART_ALLOW_DUPLICATE_IPN" %}
{% include "InvenTree/settings/setting.html" with key="PART_ALLOW_EDIT_IPN" %}
diff --git a/src/backend/InvenTree/templates/js/translated/part.js b/src/backend/InvenTree/templates/js/translated/part.js
index d369f26cbc..8606c3b84b 100644
--- a/src/backend/InvenTree/templates/js/translated/part.js
+++ b/src/backend/InvenTree/templates/js/translated/part.js
@@ -135,6 +135,7 @@ function partFields(options={}) {
},
name: {},
IPN: {},
+ revision_of: {},
revision: {
icon: 'fa-code-branch',
},
@@ -227,6 +228,7 @@ function partFields(options={}) {
// Pop 'revision' field
if (!global_settings.PART_ENABLE_REVISION) {
delete fields['revision'];
+ delete fields['revision_of'];
}
if (options.create || options.duplicate) {
diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx
index 1adaf03d04..24818dccbe 100644
--- a/src/frontend/src/App.tsx
+++ b/src/frontend/src/App.tsx
@@ -29,4 +29,10 @@ export function setApiDefaults() {
}
}
-export const queryClient = new QueryClient();
+export const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ refetchOnWindowFocus: false
+ }
+ }
+});
diff --git a/src/frontend/src/components/details/Details.tsx b/src/frontend/src/components/details/Details.tsx
index c140c98dac..d3675bceaa 100644
--- a/src/frontend/src/components/details/Details.tsx
+++ b/src/frontend/src/components/details/Details.tsx
@@ -30,17 +30,6 @@ import { StylishText } from '../items/StylishText';
import { getModelInfo } from '../render/ModelType';
import { StatusRenderer } from '../render/StatusRenderer';
-export type PartIconsType = {
- assembly: boolean;
- template: boolean;
- component: boolean;
- trackable: boolean;
- purchaseable: boolean;
- saleable: boolean;
- virtual: boolean;
- active: boolean;
-};
-
export type DetailsField =
| {
hidden?: boolean;
diff --git a/src/frontend/src/components/details/PartIcons.tsx b/src/frontend/src/components/details/PartIcons.tsx
deleted file mode 100644
index 78086bc628..0000000000
--- a/src/frontend/src/components/details/PartIcons.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import { Trans, t } from '@lingui/macro';
-import { Badge, Tooltip } from '@mantine/core';
-
-import { InvenTreeIcon, InvenTreeIconType } from '../../functions/icons';
-
-/**
- * Fetches and wraps an InvenTreeIcon in a flex div
- * @param icon name of icon
- *
- */
-function PartIcon(icon: InvenTreeIconType) {
- return (
-
-
-
- );
-}
-
-/**
- * Generates a table cell with Part icons.
- * Only used for Part Model Details
- */
-export function PartIcons({ part }: { part: any }) {
- return (
-