From 19182bacd00a6a8e275734fe6a2d31d3f3db5c5f Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 27 May 2026 18:50:52 +1000 Subject: [PATCH 1/4] [UI] Adjust table auto height (#12016) * [UI] Adjust table auto height - Better height selection - Tested against more edge cases * Fix comment --- src/frontend/src/tables/InvenTreeTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/tables/InvenTreeTable.tsx b/src/frontend/src/tables/InvenTreeTable.tsx index 993080d46b..fc0395ffce 100644 --- a/src/frontend/src/tables/InvenTreeTable.tsx +++ b/src/frontend/src/tables/InvenTreeTable.tsx @@ -814,9 +814,9 @@ export function InvenTreeTableInternal>({ }, [tableProps.onCellClick, tableProps.onRowClick, tableProps.modelType]); // When sticky headers are enabled, we adjust the maximum viewport height, - // based on the number of records being displayed (up to a maximum of 60vh) + // based on the number of records being displayed (up to a maximum of 80vh) const autoHeight = useMemo(() => { - const rows = Math.max(10, Math.min(tableState.records.length, 60)); + const rows = Math.min(80, 6 * Math.max(tableState.records.length, 3)); return `${rows}vh`; }, [tableState.records]); From 33483a3824bc28ce16d06bfca9b8a41416d26b9a Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 27 May 2026 20:35:36 +1000 Subject: [PATCH 2/4] Plugin validation tweak (#12013) * Prevent plugin validation actions during data import/export * Simplify logic * Further checks --- src/backend/InvenTree/InvenTree/helpers.py | 32 +++--- src/backend/InvenTree/InvenTree/models.py | 32 ++++-- src/backend/InvenTree/common/models.py | 4 + src/backend/InvenTree/part/models.py | 109 +++++++++++---------- src/backend/InvenTree/stock/models.py | 53 +++++----- 5 files changed, 128 insertions(+), 102 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/helpers.py b/src/backend/InvenTree/InvenTree/helpers.py index 502ac9c3b0..7b7cef24d5 100644 --- a/src/backend/InvenTree/InvenTree/helpers.py +++ b/src/backend/InvenTree/InvenTree/helpers.py @@ -550,29 +550,31 @@ def increment_serial_number(serial, part=None): incremented value, or None if incrementing could not be performed. """ from InvenTree.exceptions import log_error + from InvenTree.ready import isReadOnlyCommand from plugin import PluginMixinEnum, registry # Ensure we start with a string value if serial is not None: serial = str(serial).strip() - # First, let any plugins attempt to increment the serial number - for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): - try: - if not hasattr(plugin, 'increment_serial_number'): - continue + if not isReadOnlyCommand(): + # First, let any plugins attempt to increment the serial number + for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): + try: + if not hasattr(plugin, 'increment_serial_number'): + continue - signature = inspect.signature(plugin.increment_serial_number) + signature = inspect.signature(plugin.increment_serial_number) - # Note: 2024-08-21 - The 'part' parameter has been added to the signature - if 'part' in signature.parameters: - result = plugin.increment_serial_number(serial, part=part) - else: - result = plugin.increment_serial_number(serial) - if result is not None: - return str(result) - except Exception: - log_error('increment_serial_number', plugin=plugin.slug) + # Note: 2024-08-21 - The 'part' parameter has been added to the signature + if 'part' in signature.parameters: + result = plugin.increment_serial_number(serial, part=part) + else: + result = plugin.increment_serial_number(serial) + if result is not None: + return str(result) + except Exception: + log_error('increment_serial_number', plugin=plugin.slug) # If we get to here, no plugins were able to "increment" the provided serial value # Attempt to perform increment according to some basic rules diff --git a/src/backend/InvenTree/InvenTree/models.py b/src/backend/InvenTree/InvenTree/models.py index b6f5692c0a..2155b2296a 100644 --- a/src/backend/InvenTree/InvenTree/models.py +++ b/src/backend/InvenTree/InvenTree/models.py @@ -92,10 +92,23 @@ class PluginValidationMixin(DiffMixin): Any model class which inherits from this mixin will be exposed to the plugin validation system. """ + def should_plugin_validate(self): + """Return True if this model instance should be validated by plugins. + + The default implementation returns True, but this can be overridden in the implementing class if required. + """ + from InvenTree.ready import isReadOnlyCommand + + # Prevent plugin validation when importing or exporting data + return not isReadOnlyCommand() + def run_plugin_validation(self): """Throw this model against the plugin validation interface.""" from plugin import PluginMixinEnum, registry + if not self.should_plugin_validate(): + return + deltas = self.get_field_deltas() for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): @@ -139,15 +152,16 @@ class PluginValidationMixin(DiffMixin): from InvenTree.exceptions import log_error from plugin import PluginMixinEnum, registry - for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): - try: - plugin.validate_model_deletion(self) - except ValidationError as e: - # Plugin might raise a ValidationError to prevent deletion - raise e - except Exception: - log_error('validate_model_deletion', plugin=plugin.slug) - continue + if self.should_plugin_validate(): + for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): + try: + plugin.validate_model_deletion(self) + except ValidationError as e: + # Plugin might raise a ValidationError to prevent deletion + raise e + except Exception: + log_error('validate_model_deletion', plugin=plugin.slug) + continue super().delete(*args, **kwargs) diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py index a53d2640b4..022efae6a4 100644 --- a/src/backend/InvenTree/common/models.py +++ b/src/backend/InvenTree/common/models.py @@ -2875,6 +2875,10 @@ class Parameter( except ValidationError as e: raise ValidationError({'data': e.message}) + if InvenTree.ready.isReadOnlyCommand(): + # Skip plugin validation checks during read-only management commands + return + # Finally, run custom validation checks (via plugins) from plugin import PluginMixinEnum, registry diff --git a/src/backend/InvenTree/part/models.py b/src/backend/InvenTree/part/models.py index 48b0bb0399..55e26db261 100644 --- a/src/backend/InvenTree/part/models.py +++ b/src/backend/InvenTree/part/models.py @@ -724,19 +724,21 @@ class Part( """ from plugin import PluginMixinEnum, registry - for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): - # Run the name through each custom validator - # If the plugin returns 'True' we will skip any subsequent validation + # Skip plugin validation checks during read-only management commands + if not InvenTree.ready.isReadOnlyCommand(): + for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): + # Run the name through each custom validator + # If the plugin returns 'True' we will skip any subsequent validation - try: - result = plugin.validate_part_name(self.name, self) - if result: - return - except ValidationError as exc: - if raise_error: - raise ValidationError({'name': exc.message}) - except Exception: - log_error('validate_part_name', plugin=plugin.slug) + try: + result = plugin.validate_part_name(self.name, self) + if result: + return + except ValidationError as exc: + if raise_error: + raise ValidationError({'name': exc.message}) + except Exception: + log_error('validate_part_name', plugin=plugin.slug) def validate_ipn(self, raise_error=True): """Ensure that the IPN (internal part number) is valid for this Part". @@ -746,18 +748,20 @@ class Part( """ from plugin import PluginMixinEnum, registry - for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): - try: - result = plugin.validate_part_ipn(self.IPN, self) + # Skip plugin validation checks during read-only management commands + if not InvenTree.ready.isReadOnlyCommand(): + for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): + try: + result = plugin.validate_part_ipn(self.IPN, self) - if result: - # A "true" result force skips any subsequent checks - break - except ValidationError as exc: - if raise_error: - raise ValidationError({'IPN': exc.message}) - except Exception: - log_error('validate_part_ipn', plugin=plugin.slug) + if result: + # A "true" result force skips any subsequent checks + break + except ValidationError as exc: + if raise_error: + raise ValidationError({'IPN': exc.message}) + except Exception: + log_error('validate_part_ipn', plugin=plugin.slug) # If we get to here, none of the plugins have raised an error pattern = get_global_setting('PART_IPN_REGEX', '', create=False).strip() @@ -835,40 +839,41 @@ class Part( Raises: ValidationError if serial number is invalid and raise_error = True """ - serial = str(serial).strip() - - # First, throw the serial number against each of the loaded validation plugins from plugin import PluginMixinEnum, registry - for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): - # Run the serial number through each custom validator - # If the plugin returns 'True' we will skip any subsequent validation + serial = str(serial).strip() - try: - result = False + if not InvenTree.ready.isReadOnlyCommand(): + # First, throw the serial number against each of the loaded validation plugins + for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): + # Run the serial number through each custom validator + # If the plugin returns 'True' we will skip any subsequent validation - if hasattr(plugin, 'validate_serial_number'): - signature = inspect.signature(plugin.validate_serial_number) + try: + result = False - if 'stock_item' in signature.parameters: - # 2024-08-21: New method signature accepts a 'stock_item' parameter - result = plugin.validate_serial_number( - serial, self, stock_item=stock_item - ) + if hasattr(plugin, 'validate_serial_number'): + signature = inspect.signature(plugin.validate_serial_number) + + if 'stock_item' in signature.parameters: + # 2024-08-21: New method signature accepts a 'stock_item' parameter + result = plugin.validate_serial_number( + serial, self, stock_item=stock_item + ) + else: + # Old method signature - does not accept a 'stock_item' parameter + result = plugin.validate_serial_number(serial, self) + + if result is True: + return True + except ValidationError as exc: + if raise_error: + # Re-throw the error + raise exc else: - # Old method signature - does not accept a 'stock_item' parameter - result = plugin.validate_serial_number(serial, self) - - if result is True: - return True - except ValidationError as exc: - if raise_error: - # Re-throw the error - raise exc - else: - return False - except Exception: - log_error('validate_serial_number', plugin=plugin.slug) + return False + except Exception: + log_error('validate_serial_number', plugin=plugin.slug) """ If we are here, none of the loaded plugins (if any) threw an error or exited early @@ -960,7 +965,7 @@ class Part( """ from plugin import PluginMixinEnum, registry - if allow_plugins: + if allow_plugins and not InvenTree.ready.isReadOnlyCommand(): # Check with plugin system # If any plugin returns a non-null result, that takes priority for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index a89a94d26d..94838ea09b 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -779,24 +779,24 @@ class StockItem( # First, let any plugins convert this serial number to an integer value # If a non-null value is returned (by any plugin) we will use that + if not InvenTree.ready.isReadOnlyCommand(): + for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): + try: + serial_int = plugin.convert_serial_to_int(serial) + except Exception: + InvenTree.exceptions.log_error( + 'convert_serial_to_int', plugin=plugin.slug + ) + serial_int = None - for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): - try: - serial_int = plugin.convert_serial_to_int(serial) - except Exception: - InvenTree.exceptions.log_error( - 'convert_serial_to_int', plugin=plugin.slug - ) - serial_int = None - - # Save the first returned result - if serial_int is not None: - # Ensure that it is clipped within a range allowed in the database schema - clip = 0x7FFFFFFF - serial_int = abs(serial_int) - serial_int = min(serial_int, clip) - # Return the first non-null value - return serial_int + # Save the first returned result + if serial_int is not None: + # Ensure that it is clipped within a range allowed in the database schema + clip = 0x7FFFFFFF + serial_int = abs(serial_int) + serial_int = min(serial_int, clip) + # Return the first non-null value + return serial_int # None of the plugins provided a valid integer value if serial not in [None, '']: @@ -922,15 +922,16 @@ class StockItem( """ from plugin import PluginMixinEnum, registry - for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): - try: - plugin.validate_batch_code(self.batch, self) - except ValidationError as exc: - raise ValidationError({'batch': exc.message}) - except Exception: - InvenTree.exceptions.log_error( - 'validate_batch_code', plugin=plugin.slug - ) + if not InvenTree.ready.isReadOnlyCommand(): + for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): + try: + plugin.validate_batch_code(self.batch, self) + except ValidationError as exc: + raise ValidationError({'batch': exc.message}) + except Exception: + InvenTree.exceptions.log_error( + 'validate_batch_code', plugin=plugin.slug + ) def clean(self): """Validate the StockItem object (separate to field validation). From a9549c2e0722c56260368728896699b76a538967 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 27 May 2026 23:41:40 +1000 Subject: [PATCH 3/4] Count to location (#12019) * Add "location" field to StockCountSerializer * Adjust location on stock count * Add unit tests * Add docs * Update API and CHANGELOG --- CHANGELOG.md | 1 + docs/docs/stock/adjust.md | 2 + .../InvenTree/InvenTree/api_version.py | 5 +- src/backend/InvenTree/stock/models.py | 9 ++ src/backend/InvenTree/stock/serializers.py | 18 ++++ src/backend/InvenTree/stock/test_api.py | 82 +++++++++++++++++++ src/frontend/src/forms/StockForms.tsx | 9 ++ 7 files changed, 125 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45f4a02dd5..78500f9f4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- [#12019](https://github.com/inventree/InvenTree/pull/12019) adds a "location" field to the StockCount API endpoint, allowing users to specify a location when performing a stock count. This field is optional, and if not provided, the stock count will be performed without changing the location of the stock item. If a location is provided, the stock item(s) will be moved to the specified location as part of the stock count operation. - [#12011](https://github.com/inventree/InvenTree/pull/12011) adds a "creation_date" field to the StockItem API endpoint, allowing users to track when each stock item was created. This field is read-only and is automatically set to the current date and time when a new stock item is created. - [#12000](https://github.com/inventree/InvenTree/pull/12000) adds support for auto-allocation of stock items against sales orders. This includes both backend and frontend changes, allowing users to trigger auto-allocation via the API or through the UI. The auto-allocation process will attempt to allocate available stock items to the sales order line items, based on the specified stock sorting and allocation rules. - [#11920](https://github.com/inventree/InvenTree/pull/11920) adds support for renaming attachments after they have been uploaded. This includes both backend and frontend changes, allowing users to rename attachments via the API or through the UI. diff --git a/docs/docs/stock/adjust.md b/docs/docs/stock/adjust.md index 69cf5eaa2c..c536bf0670 100644 --- a/docs/docs/stock/adjust.md +++ b/docs/docs/stock/adjust.md @@ -32,6 +32,8 @@ Remove parts from a stock item record - for example taking parts from stock for Count stock items (stocktake) to record the number of items in stock at a given point of time. The quantity for each part is pre-filled with the current quantity based on stock item history. +An optional **Location** field allows all counted items to be moved to a new location in the same operation. If left blank, each item retains its current location. + {{ image("stock/stock_count.png", "Stock Count") }} ### Merge Stock diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 68c7f97f70..82d1c6dba3 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,11 +1,14 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 496 +INVENTREE_API_VERSION = 497 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v497 -> 2026-05-27 : https://github.com/inventree/InvenTree/pull/12019 + - Adds "location" field to StockCount API endpoint + v496 -> 2026-05-26 : https://github.com/inventree/InvenTree/pull/12011 - Add "creation_date" field to the StockItem API endpoint diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index 94838ea09b..58f63f5422 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -2624,6 +2624,7 @@ class StockItem( Keyword Arguments: notes: Optional notes for the stocktake status: Optionally adjust the stock status + location: Optionally set the stock location """ try: count = Decimal(count) @@ -2635,6 +2636,14 @@ class StockItem( tracking_info = {} + location = kwargs.pop('location', None) + + if location and location != self.location: + old_location = self.location + self.location = location + tracking_info['location'] = location.pk + tracking_info['old_location'] = old_location.pk if old_location else None + status = kwargs.pop('status', None) or kwargs.pop('status_custom_key', None) if status and not self.compare_status(status): diff --git a/src/backend/InvenTree/stock/serializers.py b/src/backend/InvenTree/stock/serializers.py index 0bb12450a8..31b400e18c 100644 --- a/src/backend/InvenTree/stock/serializers.py +++ b/src/backend/InvenTree/stock/serializers.py @@ -1744,6 +1744,20 @@ class StockAdjustmentSerializer(serializers.Serializer): class StockCountSerializer(StockAdjustmentSerializer): """Serializer for counting stock items.""" + class Meta: + """Metaclass options.""" + + fields = ['items', 'notes', 'location'] + + location = serializers.PrimaryKeyRelatedField( + queryset=StockLocation.objects.filter(structural=False), + many=False, + required=False, + allow_null=True, + label=_('Location'), + help_text=_('Set stock location for counted items (optional)'), + ) + def save(self): """Count stock.""" request = self.context['request'] @@ -1751,6 +1765,7 @@ class StockCountSerializer(StockAdjustmentSerializer): data = self.validated_data items = data['items'] notes = data.get('notes', '') + location = data.get('location', None) with transaction.atomic(): for item in items: @@ -1764,6 +1779,9 @@ class StockCountSerializer(StockAdjustmentSerializer): if field_value := item.get(field_name, None): extra[field_name] = field_value + if location is not None: + extra['location'] = location + stock_item.stocktake(quantity, request.user, notes=notes, **extra) diff --git a/src/backend/InvenTree/stock/test_api.py b/src/backend/InvenTree/stock/test_api.py index 27de523ec8..b8e1000f63 100644 --- a/src/backend/InvenTree/stock/test_api.py +++ b/src/backend/InvenTree/stock/test_api.py @@ -2253,6 +2253,88 @@ class StocktakeTest(StockAPITestCase): status_code=status.HTTP_400_BAD_REQUEST, ) + def test_count_with_location(self): + """Test that the stock count endpoint correctly handles the optional location field.""" + url = reverse('api-stock-count') + + # Stock item pk=1234 starts at location 5; pk=1 starts at location 3 + item_a = StockItem.objects.get(pk=1234) + item_b = StockItem.objects.get(pk=1) + + self.assertEqual(item_a.location.pk, 5) + self.assertEqual(item_b.location.pk, 3) + + # --- location is updated when provided (single item) --- + response = self.post( + url, + {'items': [{'pk': item_a.pk, 'quantity': 10}], 'location': 1}, + expected_code=201, + ) + self.assertEqual(response.data['items'][0]['pk'], item_a.pk) + + item_a.refresh_from_db() + self.assertEqual(item_a.location.pk, 1) + + # Tracking entry records the location change + entry = StockItemTracking.objects.filter( + item=item_a, tracking_type=StockHistoryCode.STOCK_COUNT + ).latest('date') + self.assertEqual(entry.deltas.get('location'), 1) + self.assertEqual(entry.deltas.get('old_location'), 5) + + # --- location is updated for multiple items simultaneously --- + response = self.post( + url, + { + 'items': [ + {'pk': item_a.pk, 'quantity': 5}, + {'pk': item_b.pk, 'quantity': 20}, + ], + 'location': 2, + }, + expected_code=201, + ) + self.assertEqual(len(response.data['items']), 2) + + item_a.refresh_from_db() + item_b.refresh_from_db() + self.assertEqual(item_a.location.pk, 2) + self.assertEqual(item_b.location.pk, 2) + + # Both items have a tracking entry with the new location + for item, old_loc in [(item_a, 1), (item_b, 3)]: + entry = StockItemTracking.objects.filter( + item=item, tracking_type=StockHistoryCode.STOCK_COUNT + ).latest('date') + self.assertEqual(entry.deltas.get('location'), 2) + self.assertEqual(entry.deltas.get('old_location'), old_loc) + + # --- location is unchanged when not provided --- + response = self.post( + url, {'items': [{'pk': item_a.pk, 'quantity': 7}]}, expected_code=201 + ) + + item_a.refresh_from_db() + # Location should still be 2 (unchanged from the previous count) + self.assertEqual(item_a.location.pk, 2) + + # Tracking entry has no location delta when location was not provided + entry = StockItemTracking.objects.filter( + item=item_a, tracking_type=StockHistoryCode.STOCK_COUNT + ).latest('date') + self.assertNotIn('location', entry.deltas) + self.assertNotIn('old_location', entry.deltas) + + # --- structural location is rejected --- + structural = StockLocation.objects.create(name='Structural', structural=True) + + response = self.post( + url, + {'items': [{'pk': item_a.pk, 'quantity': 1}], 'location': structural.pk}, + expected_code=400, + ) + self.assertIn('does not exist', str(response.data['location'])) + class StockItemDeletionTest(StockAPITestCase): """Tests for stock item deletion via the API.""" diff --git a/src/frontend/src/forms/StockForms.tsx b/src/frontend/src/forms/StockForms.tsx index 547d2b2087..425b76e1bc 100644 --- a/src/frontend/src/forms/StockForms.tsx +++ b/src/frontend/src/forms/StockForms.tsx @@ -967,6 +967,9 @@ function stockCountFields(items: any[]): ApiFormFieldSet { const initialValue = mapAdjustmentItems(items); + // Extract all location values from the items + const locations = [...new Set(items.map((item) => item.location))]; + const fields: ApiFormFieldSet = { items: { field_type: 'table', @@ -990,6 +993,12 @@ function stockCountFields(items: any[]): ApiFormFieldSet { { title: t`Actions` } ] }, + location: { + value: locations.length === 1 ? locations[0] : undefined, + filters: { + structural: false + } + }, notes: {} }; From f3be14467d857e66542392534197d3a88b69ab55 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 28 May 2026 06:33:43 +1000 Subject: [PATCH 4/4] Fix text for plugin activation dialog (#12021) --- src/frontend/src/tables/plugin/PluginListTable.tsx | 2 +- src/frontend/tests/pages/pui_part.spec.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/frontend/src/tables/plugin/PluginListTable.tsx b/src/frontend/src/tables/plugin/PluginListTable.tsx index 9d6bd20f21..83899ec184 100644 --- a/src/frontend/src/tables/plugin/PluginListTable.tsx +++ b/src/frontend/src/tables/plugin/PluginListTable.tsx @@ -261,7 +261,7 @@ export default function PluginListTable() { const [pluginPackage, setPluginPackage] = useState(''); const activatePluginModal = useEditApiFormModal({ - title: t`Activate Plugin`, + title: activate ? t`Activate Plugin` : t`Deactivate Plugin`, url: ApiEndpoints.plugin_activate, pathParams: { key: selectedPluginKey }, preFormContent: activateModalContent, diff --git a/src/frontend/tests/pages/pui_part.spec.ts b/src/frontend/tests/pages/pui_part.spec.ts index 72726aa541..39f2cc07cf 100644 --- a/src/frontend/tests/pages/pui_part.spec.ts +++ b/src/frontend/tests/pages/pui_part.spec.ts @@ -290,8 +290,6 @@ test('Parts - BOM Validation', async ({ browser }) => { // Edit line item, to ensure BOM is not valid const cell = await page.getByRole('cell', { name: 'paint', exact: true }); - // await cell.click({ button: 'right' }); - // await page.getByRole('button', { name: 'Edit', exact: true }).click(); await clickOnRowMenu(cell); await page.getByRole('menuitem', { name: 'Edit', exact: true }).click();