mirror of
https://github.com/inventree/InvenTree.git
synced 2026-02-25 16:17:58 +00:00
Supplier Part Updates (#11303)
* Add "primary" field to SupplierPart model * Remove "default_supplier" field from the Part model * Ensure only one SupplierPart can be "primary" for a given Part * Update references to "default_supplier" * Add 'primary' field to the SupplierPart API serializer * update SupplierPart table * Use bulk-update operations * Bug fix for data migration * Allow ordering by 'primary' field * Tweak import message * Edit 'primary' field in UI * Fix checks in save() methods * Better table updates * Update CHANGELOG * Bump API version * Fix unit test * Add unit test for API * Playwright tests
This commit is contained in:
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
|
- [#11303](https://github.com/inventree/InvenTree/pull/11303) removes the `default_supplier` field from the `Part` model. Instead, the `SupplierPart` model now has a `primary` field which is used to indicate which supplier is the default for a given part. Any external client applications which made use of the old `default_supplier` field will need to be updated.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
[#11222](https://github.com/inventree/InvenTree/pull/11222) adds support for data import using natural keys, allowing for easier association of related objects without needing to know their internal database IDs.
|
[#11222](https://github.com/inventree/InvenTree/pull/11222) adds support for data import using natural keys, allowing for easier association of related objects without needing to know their internal database IDs.
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 455
|
INVENTREE_API_VERSION = 456
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v456 -> 2026-02-20 : https://github.com/inventree/InvenTree/pull/11303
|
||||||
|
- Adds "primary" field to the SupplierPart API
|
||||||
|
- Removes "default_supplier" field from the Part API
|
||||||
|
|
||||||
v455 -> 2026-02-19 : https://github.com/inventree/InvenTree/pull/11383
|
v455 -> 2026-02-19 : https://github.com/inventree/InvenTree/pull/11383
|
||||||
- Adds "exists_for_model_id" filter to ParameterTemplate API endpoint
|
- Adds "exists_for_model_id" filter to ParameterTemplate API endpoint
|
||||||
- Adds "exists_for_related_model" filter to ParameterTemplate API endpoint
|
- Adds "exists_for_related_model" filter to ParameterTemplate API endpoint
|
||||||
|
|||||||
@@ -249,6 +249,8 @@ class SupplierPartFilter(FilterSet):
|
|||||||
|
|
||||||
active = rest_filters.BooleanFilter(label=_('Supplier Part is Active'))
|
active = rest_filters.BooleanFilter(label=_('Supplier Part is Active'))
|
||||||
|
|
||||||
|
primary = rest_filters.BooleanFilter(label=_('Primary Supplier Part'))
|
||||||
|
|
||||||
# Filter by 'active' status of linked part
|
# Filter by 'active' status of linked part
|
||||||
part_active = rest_filters.BooleanFilter(
|
part_active = rest_filters.BooleanFilter(
|
||||||
field_name='part__active', label=_('Internal Part is Active')
|
field_name='part__active', label=_('Internal Part is Active')
|
||||||
@@ -366,6 +368,7 @@ class SupplierPartList(
|
|||||||
'supplier',
|
'supplier',
|
||||||
'manufacturer',
|
'manufacturer',
|
||||||
'active',
|
'active',
|
||||||
|
'primary',
|
||||||
'IPN',
|
'IPN',
|
||||||
'MPN',
|
'MPN',
|
||||||
'SKU',
|
'SKU',
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 5.2.11 on 2026-02-12 10:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("company", "0077_delete_manufacturerpartparameter"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="supplierpart",
|
||||||
|
name="primary",
|
||||||
|
field=models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Is this the primary supplier part for the linked Part?",
|
||||||
|
verbose_name="Primary",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Generated by Django 5.2.11 on 2026-02-12 10:54
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def link_primary_supplier_part(apps, schema_editor):
|
||||||
|
"""Mark 'primary' SupplierPart for each Part, if one exists."""
|
||||||
|
|
||||||
|
Part = apps.get_model("part", "Part")
|
||||||
|
SupplierPart = apps.get_model("company", "SupplierPart")
|
||||||
|
|
||||||
|
# Find any part which links to a "default_supplier"
|
||||||
|
primary_supplier_ids = Part.objects.exclude(
|
||||||
|
default_supplier=None,
|
||||||
|
).values_list("default_supplier_id", flat=True).distinct()
|
||||||
|
|
||||||
|
if len(primary_supplier_ids) > 0:
|
||||||
|
# Mark the relevant SupplierPart objects as "primary"
|
||||||
|
SupplierPart.objects.filter(
|
||||||
|
pk__in=primary_supplier_ids
|
||||||
|
).update(primary=True)
|
||||||
|
|
||||||
|
print(f"Marked {len(primary_supplier_ids)} SupplierPart objects as primary")
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_link_primary_supplier_part(apps, schema_editor):
|
||||||
|
"""Add 'primary' SupplierPart for each Part."""
|
||||||
|
|
||||||
|
SupplierPart = apps.get_model("company", "SupplierPart")
|
||||||
|
|
||||||
|
# Find any SupplierPart object marked as "primary"
|
||||||
|
primary_supplier_parts = SupplierPart.objects.filter(primary=True)
|
||||||
|
|
||||||
|
if len(primary_supplier_parts) > 0:
|
||||||
|
# Unmark the relevant SupplierPart objects as "primary"
|
||||||
|
|
||||||
|
for supplier_part in primary_supplier_parts:
|
||||||
|
supplier_part.part.default_supplier = supplier_part
|
||||||
|
supplier_part.part.save()
|
||||||
|
|
||||||
|
print(f"Linked {len(primary_supplier_parts)} primary SupplierPart objects")
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("company", "0078_supplierpart_primary"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
code=link_primary_supplier_part,
|
||||||
|
reverse_code=reverse_link_primary_supplier_part,
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -373,22 +373,19 @@ class Address(InvenTree.models.InvenTreeModel):
|
|||||||
Rules:
|
Rules:
|
||||||
- If this address is marked as "primary", ensure that all other addresses for this company are marked as non-primary
|
- If this address is marked as "primary", ensure that all other addresses for this company are marked as non-primary
|
||||||
"""
|
"""
|
||||||
others = list(
|
others = Address.objects.filter(company=self.company).exclude(pk=self.pk)
|
||||||
Address.objects.filter(company=self.company).exclude(pk=self.pk).all()
|
|
||||||
)
|
|
||||||
|
|
||||||
# If this is the *only* address for this company, make it the primary one
|
# If this is the *only* address for this company, make it the primary one
|
||||||
if len(others) == 0:
|
if not others.exists():
|
||||||
self.primary = True
|
self.primary = True
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
# Once this address is saved, check others
|
# Once this address is saved, check others
|
||||||
if self.primary:
|
if self.primary:
|
||||||
for addr in others:
|
Address.objects.filter(company=self.company).exclude(pk=self.pk).filter(
|
||||||
if addr.primary:
|
primary=True
|
||||||
addr.primary = False
|
).update(primary=False)
|
||||||
addr.save()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_api_url():
|
def get_api_url():
|
||||||
@@ -612,6 +609,7 @@ class SupplierPart(
|
|||||||
source_item: The sourcing item linked to this SupplierPart instance
|
source_item: The sourcing item linked to this SupplierPart instance
|
||||||
supplier: Company that supplies this SupplierPart object
|
supplier: Company that supplies this SupplierPart object
|
||||||
active: Boolean value, is this supplier part active
|
active: Boolean value, is this supplier part active
|
||||||
|
primary: Boolean value, is this the primary supplier part for the linked Part
|
||||||
SKU: Stock keeping unit (supplier part number)
|
SKU: Stock keeping unit (supplier part number)
|
||||||
link: Link to external website for this supplier part
|
link: Link to external website for this supplier part
|
||||||
description: Descriptive notes field
|
description: Descriptive notes field
|
||||||
@@ -739,8 +737,21 @@ class SupplierPart(
|
|||||||
self.clean()
|
self.clean()
|
||||||
self.validate_unique()
|
self.validate_unique()
|
||||||
|
|
||||||
|
# Ensure that only one SupplierPart is marked as "primary" for a given Part
|
||||||
|
others = SupplierPart.objects.filter(part=self.part).exclude(pk=self.pk)
|
||||||
|
|
||||||
|
# If this is the *only* SupplierPart for this Part, make it the primary one
|
||||||
|
if not others.exists():
|
||||||
|
self.primary = True
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
# Once this SupplierPart is saved, check others
|
||||||
|
if self.primary:
|
||||||
|
SupplierPart.objects.filter(part=self.part).exclude(pk=self.pk).filter(
|
||||||
|
primary=True
|
||||||
|
).update(primary=False)
|
||||||
|
|
||||||
part = models.ForeignKey(
|
part = models.ForeignKey(
|
||||||
'part.Part',
|
'part.Part',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@@ -771,6 +782,12 @@ class SupplierPart(
|
|||||||
help_text=_('Is this supplier part active?'),
|
help_text=_('Is this supplier part active?'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
primary = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_('Primary'),
|
||||||
|
help_text=_('Is this the primary supplier part for the linked Part?'),
|
||||||
|
)
|
||||||
|
|
||||||
manufacturer_part = models.ForeignKey(
|
manufacturer_part = models.ForeignKey(
|
||||||
ManufacturerPart,
|
ManufacturerPart,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ class SupplierPartSerializer(
|
|||||||
'on_order',
|
'on_order',
|
||||||
'link',
|
'link',
|
||||||
'active',
|
'active',
|
||||||
|
'primary',
|
||||||
'manufacturer_detail',
|
'manufacturer_detail',
|
||||||
'manufacturer_part',
|
'manufacturer_part',
|
||||||
'manufacturer_part_detail',
|
'manufacturer_part_detail',
|
||||||
|
|||||||
@@ -712,6 +712,32 @@ class SupplierPartTest(InvenTreeAPITestCase):
|
|||||||
for result in response.data:
|
for result in response.data:
|
||||||
self.assertEqual(result['supplier'], company.pk)
|
self.assertEqual(result['supplier'], company.pk)
|
||||||
|
|
||||||
|
def test_primary(self):
|
||||||
|
"""Test for the 'primary' field in the SupplierPart model."""
|
||||||
|
for sp in SupplierPart.objects.filter(part=1):
|
||||||
|
self.patch(
|
||||||
|
reverse('api-supplier-part-detail', kwargs={'pk': sp.pk}),
|
||||||
|
{'primary': True},
|
||||||
|
expected_code=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Only one supplier part should be primary for this part
|
||||||
|
self.assertEqual(
|
||||||
|
SupplierPart.objects.filter(part=1, primary=True).count(), 1
|
||||||
|
)
|
||||||
|
|
||||||
|
# Filter via the API
|
||||||
|
response = self.get(
|
||||||
|
reverse('api-supplier-part-list'),
|
||||||
|
{'part': 1, 'primary': True},
|
||||||
|
expected_code=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(response.data), 1)
|
||||||
|
|
||||||
|
self.assertEqual(SupplierPart.objects.filter(part=1).count(), 4)
|
||||||
|
self.assertEqual(SupplierPart.objects.filter(part=1, primary=False).count(), 3)
|
||||||
|
|
||||||
def test_filterable_fields(self):
|
def test_filterable_fields(self):
|
||||||
"""Test inclusion/exclusion of optional API fields."""
|
"""Test inclusion/exclusion of optional API fields."""
|
||||||
fields = {
|
fields = {
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ from sql_util.utils import SubqueryCount, SubquerySum
|
|||||||
|
|
||||||
import build.serializers
|
import build.serializers
|
||||||
import common.filters
|
import common.filters
|
||||||
|
import company.models as company_models
|
||||||
import order.models
|
import order.models
|
||||||
import part.filters as part_filters
|
import part.filters as part_filters
|
||||||
import part.models as part_models
|
|
||||||
import stock.models
|
import stock.models
|
||||||
import stock.serializers
|
import stock.serializers
|
||||||
from company.serializers import (
|
from company.serializers import (
|
||||||
@@ -587,7 +587,7 @@ class PurchaseOrderLineItemSerializer(
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
part = serializers.PrimaryKeyRelatedField(
|
part = serializers.PrimaryKeyRelatedField(
|
||||||
queryset=part_models.SupplierPart.objects.all(),
|
queryset=company_models.SupplierPart.objects.all(),
|
||||||
many=False,
|
many=False,
|
||||||
required=True,
|
required=True,
|
||||||
allow_null=True,
|
allow_null=True,
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ class PartAdmin(admin.ModelAdmin):
|
|||||||
'variant_of',
|
'variant_of',
|
||||||
'category',
|
'category',
|
||||||
'default_location',
|
'default_location',
|
||||||
'default_supplier',
|
|
||||||
'bom_checked_by',
|
'bom_checked_by',
|
||||||
'creation_user',
|
'creation_user',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.11 on 2026-02-12 11:08
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("company", "0079_auto_20260212_1054"),
|
||||||
|
("part", "0146_auto_20251203_1241"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="part",
|
||||||
|
name="default_supplier",
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -52,7 +52,6 @@ from build.status_codes import BuildStatusGroups
|
|||||||
from common.currency import currency_code_default
|
from common.currency import currency_code_default
|
||||||
from common.icons import validate_icon
|
from common.icons import validate_icon
|
||||||
from common.settings import get_global_setting
|
from common.settings import get_global_setting
|
||||||
from company.models import SupplierPart
|
|
||||||
from InvenTree import helpers, validators
|
from InvenTree import helpers, validators
|
||||||
from InvenTree.exceptions import log_error
|
from InvenTree.exceptions import log_error
|
||||||
from InvenTree.fields import InvenTreeURLField
|
from InvenTree.fields import InvenTreeURLField
|
||||||
@@ -490,7 +489,6 @@ class Part(
|
|||||||
link: Link to an external page with more information about this part (e.g. internal Wiki)
|
link: Link to an external page with more information about this part (e.g. internal Wiki)
|
||||||
image: Image of this part
|
image: Image of this part
|
||||||
default_location: Where the item is normally stored (may be null)
|
default_location: Where the item is normally stored (may be null)
|
||||||
default_supplier: The default SupplierPart which should be used to procure and stock this part
|
|
||||||
default_expiry: The default expiry duration for any StockItem instances of this part
|
default_expiry: The default expiry duration for any StockItem instances of this part
|
||||||
minimum_stock: Minimum preferred quantity to keep in stock
|
minimum_stock: Minimum preferred quantity to keep in stock
|
||||||
units: Units of measure for this part (default='pcs')
|
units: Units of measure for this part (default='pcs')
|
||||||
@@ -1215,31 +1213,14 @@ class Part(
|
|||||||
# Default case - no default category found
|
# Default case - no default category found
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_default_supplier(self):
|
@property
|
||||||
"""Get the default supplier part for this part (may be None).
|
def default_supplier(self):
|
||||||
|
"""Return the default (primary) SupplierPart for this Part.
|
||||||
|
|
||||||
- If the part specifies a default_supplier, return that
|
This function is included for backwards compatibility,
|
||||||
- If there is only one supplier part available, return that
|
as the 'Part' model used to have a 'default_supplier' field which was a ForeignKey to SupplierPart.
|
||||||
- Else, return None
|
|
||||||
"""
|
"""
|
||||||
if self.default_supplier:
|
return self.supplier_parts.filter(primary=True).first()
|
||||||
return self.default_supplier
|
|
||||||
|
|
||||||
if self.supplier_count == 1:
|
|
||||||
return self.supplier_parts.first()
|
|
||||||
|
|
||||||
# Default to None if there are multiple suppliers to choose from
|
|
||||||
return None
|
|
||||||
|
|
||||||
default_supplier = models.ForeignKey(
|
|
||||||
SupplierPart,
|
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
verbose_name=_('Default Supplier'),
|
|
||||||
help_text=_('Default supplier part'),
|
|
||||||
related_name='default_parts',
|
|
||||||
)
|
|
||||||
|
|
||||||
default_expiry = models.PositiveIntegerField(
|
default_expiry = models.PositiveIntegerField(
|
||||||
default=0,
|
default=0,
|
||||||
|
|||||||
@@ -576,7 +576,6 @@ class PartSerializer(
|
|||||||
'default_expiry',
|
'default_expiry',
|
||||||
'default_location',
|
'default_location',
|
||||||
'default_location_detail',
|
'default_location_detail',
|
||||||
'default_supplier',
|
|
||||||
'description',
|
'description',
|
||||||
'full_name',
|
'full_name',
|
||||||
'image',
|
'image',
|
||||||
|
|||||||
@@ -184,10 +184,10 @@ class ImportPart(APIView):
|
|||||||
import_data, part=part, manufacturer_part=manufacturer_part
|
import_data, part=part, manufacturer_part=manufacturer_part
|
||||||
)
|
)
|
||||||
|
|
||||||
# set default supplier if not set
|
# Set as primary supplier if not already set
|
||||||
if not part.default_supplier:
|
if not part.default_supplier:
|
||||||
part.default_supplier = supplier_part
|
supplier_part.primary = True
|
||||||
part.save()
|
supplier_part.save()
|
||||||
|
|
||||||
# get pricing
|
# get pricing
|
||||||
pricing = supplier_plugin.get_pricing_data(import_data)
|
pricing = supplier_plugin.get_pricing_data(import_data)
|
||||||
|
|||||||
@@ -109,8 +109,8 @@ class SupplierMixin(SettingsMixin, Generic[PartData]):
|
|||||||
*,
|
*,
|
||||||
part: part_models.Part,
|
part: part_models.Part,
|
||||||
manufacturer_part: company.models.ManufacturerPart,
|
manufacturer_part: company.models.ManufacturerPart,
|
||||||
) -> part_models.SupplierPart:
|
) -> company.models.SupplierPart:
|
||||||
"""Import a supplier part using the provided data.
|
"""Import a SupplierPart using the provided data.
|
||||||
|
|
||||||
This may include:
|
This may include:
|
||||||
- Creating a new supplier part
|
- Creating a new supplier part
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ export function useSupplierPartFields({
|
|||||||
packaging: {
|
packaging: {
|
||||||
icon: <IconPackage />
|
icon: <IconPackage />
|
||||||
},
|
},
|
||||||
|
primary: {},
|
||||||
active: {}
|
active: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -55,14 +55,6 @@ export function usePartFields({
|
|||||||
structural: false
|
structural: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
default_supplier: {
|
|
||||||
hidden: !partId || !purchaseable,
|
|
||||||
filters: {
|
|
||||||
part: partId,
|
|
||||||
part_detail: true,
|
|
||||||
supplier_detail: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
default_expiry: {},
|
default_expiry: {},
|
||||||
minimum_stock: {},
|
minimum_stock: {},
|
||||||
responsible: {
|
responsible: {
|
||||||
|
|||||||
@@ -692,16 +692,6 @@ export default function PartDetail() {
|
|||||||
badge: 'owner',
|
badge: 'owner',
|
||||||
hidden: !part.responsible
|
hidden: !part.responsible
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: 'link',
|
|
||||||
name: 'default_supplier',
|
|
||||||
label: t`Default Supplier`,
|
|
||||||
model: ModelType.supplierpart,
|
|
||||||
model_formatter: (model: any) => {
|
|
||||||
return model.SKU;
|
|
||||||
},
|
|
||||||
hidden: !part.default_supplier
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'default_expiry',
|
name: 'default_expiry',
|
||||||
label: t`Default Expiry`,
|
label: t`Default Expiry`,
|
||||||
|
|||||||
@@ -99,6 +99,12 @@ export function SupplierPartTable({
|
|||||||
title: t`MPN`,
|
title: t`MPN`,
|
||||||
render: (record: any) => record?.manufacturer_part_detail?.MPN
|
render: (record: any) => record?.manufacturer_part_detail?.MPN
|
||||||
},
|
},
|
||||||
|
BooleanColumn({
|
||||||
|
accessor: 'primary',
|
||||||
|
sortable: true,
|
||||||
|
switchable: true,
|
||||||
|
defaultVisible: false
|
||||||
|
}),
|
||||||
BooleanColumn({
|
BooleanColumn({
|
||||||
accessor: 'active',
|
accessor: 'active',
|
||||||
title: t`Active`,
|
title: t`Active`,
|
||||||
@@ -176,7 +182,9 @@ export function SupplierPartTable({
|
|||||||
supplier: supplierId,
|
supplier: supplierId,
|
||||||
manufacturer_part: manufacturerPartId
|
manufacturer_part: manufacturerPartId
|
||||||
},
|
},
|
||||||
table: table,
|
onFormSuccess: (response: any) => {
|
||||||
|
table.refreshTable();
|
||||||
|
},
|
||||||
successMessage: t`Supplier part created`
|
successMessage: t`Supplier part created`
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -215,6 +223,11 @@ export function SupplierPartTable({
|
|||||||
label: t`Active`,
|
label: t`Active`,
|
||||||
description: t`Show active supplier parts`
|
description: t`Show active supplier parts`
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'primary',
|
||||||
|
label: t`Primary`,
|
||||||
|
description: t`Show primary supplier parts`
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'part_active',
|
name: 'part_active',
|
||||||
label: t`Active Part`,
|
label: t`Active Part`,
|
||||||
@@ -243,7 +256,9 @@ export function SupplierPartTable({
|
|||||||
pk: selectedSupplierPart?.pk,
|
pk: selectedSupplierPart?.pk,
|
||||||
title: t`Edit Supplier Part`,
|
title: t`Edit Supplier Part`,
|
||||||
fields: useMemo(() => editSupplierPartFields, [editSupplierPartFields]),
|
fields: useMemo(() => editSupplierPartFields, [editSupplierPartFields]),
|
||||||
table: table
|
onFormSuccess: (response: any) => {
|
||||||
|
table.refreshTable();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const duplicateSupplierPart = useCreateApiFormModal({
|
const duplicateSupplierPart = useCreateApiFormModal({
|
||||||
@@ -252,9 +267,12 @@ export function SupplierPartTable({
|
|||||||
fields: useMemo(() => editSupplierPartFields, [editSupplierPartFields]),
|
fields: useMemo(() => editSupplierPartFields, [editSupplierPartFields]),
|
||||||
initialData: {
|
initialData: {
|
||||||
...selectedSupplierPart,
|
...selectedSupplierPart,
|
||||||
|
primary: false,
|
||||||
active: true
|
active: true
|
||||||
},
|
},
|
||||||
table: table,
|
onFormSuccess: (response: any) => {
|
||||||
|
table.refreshTable();
|
||||||
|
},
|
||||||
successMessage: t`Supplier part created`
|
successMessage: t`Supplier part created`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { test } from '../baseFixtures.js';
|
import { test } from '../baseFixtures.js';
|
||||||
import {
|
import {
|
||||||
|
clearTableFilters,
|
||||||
clickOnParamFilter,
|
clickOnParamFilter,
|
||||||
loadTab,
|
loadTab,
|
||||||
navigate,
|
navigate,
|
||||||
|
setTableChoiceFilter,
|
||||||
showParametricView
|
showParametricView
|
||||||
} from '../helpers.js';
|
} from '../helpers.js';
|
||||||
import { doCachedLogin } from '../login.js';
|
import { doCachedLogin } from '../login.js';
|
||||||
@@ -63,3 +65,24 @@ test('Company - Parameters', async ({ browser }) => {
|
|||||||
await page.getByRole('cell', { name: 'Arrow Electronics' }).waitFor();
|
await page.getByRole('cell', { name: 'Arrow Electronics' }).waitFor();
|
||||||
await page.getByRole('cell', { name: 'PCB assembly house' }).waitFor();
|
await page.getByRole('cell', { name: 'PCB assembly house' }).waitFor();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Company - Supplier Parts', async ({ browser }) => {
|
||||||
|
const page = await doCachedLogin(browser, {
|
||||||
|
username: 'steven',
|
||||||
|
password: 'wizardstaff',
|
||||||
|
url: 'purchasing/index/suppliers'
|
||||||
|
});
|
||||||
|
|
||||||
|
await loadTab(page, 'Supplier Parts');
|
||||||
|
await clearTableFilters(page);
|
||||||
|
|
||||||
|
await page.getByText('- 25 / 777').waitFor();
|
||||||
|
|
||||||
|
await setTableChoiceFilter(page, 'Primary', 'Yes');
|
||||||
|
await page.getByText('- 25 / 318').waitFor();
|
||||||
|
|
||||||
|
await clearTableFilters(page);
|
||||||
|
|
||||||
|
await setTableChoiceFilter(page, 'Primary', 'No');
|
||||||
|
await page.getByText('- 25 / 459').waitFor();
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user