2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-05-06 09:43:38 +00:00

Prevent delete serialized stock (#11872)

* Add setting to prevent deletion of serialized stock

Co-authored-by: Copilot <copilot@github.com>

* Add unit test

Co-authored-by: Copilot <copilot@github.com>

* Update CHANGELOG

Co-authored-by: Copilot <copilot@github.com>

---------

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Oliver
2026-05-05 10:43:02 +10:00
committed by GitHub
parent 3c3964062f
commit 18e3c5a0e9
6 changed files with 48 additions and 0 deletions
+1
View File
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- [#11872](https://github.com/inventree/InvenTree/pull/11872) adds a global setting to allow or disallow the deletion of serialized stock items.
- [#11861](https://github.com/inventree/InvenTree/pull/11861) adds support for bulk-replacing a component in multiple BOMs simultaneously - [#11861](https://github.com/inventree/InvenTree/pull/11861) adds support for bulk-replacing a component in multiple BOMs simultaneously
- [#11853](https://github.com/inventree/InvenTree/pull/11853) adds BOM comparison functionality, allowing users to compare the BOM of one assembly with another assembly. - [#11853](https://github.com/inventree/InvenTree/pull/11853) adds BOM comparison functionality, allowing users to compare the BOM of one assembly with another assembly.
- [#11809](https://github.com/inventree/InvenTree/pull/11809) adds multi-level subassembly display mode to the BOM table, allowing users to view multiple levels of subassemblies in a single table view. This is an optional display mode which can be toggled on or off by the user. - [#11809](https://github.com/inventree/InvenTree/pull/11809) adds multi-level subassembly display mode to the BOM table, allowing users to view multiple levels of subassemblies in a single table view. This is an optional display mode which can be toggled on or off by the user.
+1
View File
@@ -206,6 +206,7 @@ Configuration of stock item options
| Name | Description | Default | Units | | Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- | | ---- | ----------- | ------- | ----- |
{{ globalsetting("SERIAL_NUMBER_GLOBALLY_UNIQUE") }} {{ globalsetting("SERIAL_NUMBER_GLOBALLY_UNIQUE") }}
{{ globalsetting("STOCK_ALLOW_DELETE_SERIALIZED") }}
{{ globalsetting("STOCK_DELETE_DEPLETED_DEFAULT") }} {{ globalsetting("STOCK_DELETE_DEPLETED_DEFAULT") }}
{{ globalsetting("STOCK_BATCH_CODE_TEMPLATE") }} {{ globalsetting("STOCK_BATCH_CODE_TEMPLATE") }}
{{ globalsetting("STOCK_ENABLE_EXPIRY") }} {{ globalsetting("STOCK_ENABLE_EXPIRY") }}
@@ -716,6 +716,12 @@ SYSTEM_SETTINGS: dict[str, InvenTreeSettingsKeyType] = {
'default': True, 'default': True,
'validator': bool, 'validator': bool,
}, },
'STOCK_ALLOW_DELETE_SERIALIZED': {
'name': _('Delete Serialized Stock'),
'description': _('Allow deletion of stock items which have a serial number'),
'default': True,
'validator': bool,
},
'STOCK_BATCH_CODE_TEMPLATE': { 'STOCK_BATCH_CODE_TEMPLATE': {
'name': _('Batch Code Template'), 'name': _('Batch Code Template'),
'description': _('Template for generating default batch codes for stock items'), 'description': _('Template for generating default batch codes for stock items'),
+8
View File
@@ -449,6 +449,14 @@ class StockItem(
order_insertion_by = ['part'] order_insertion_by = ['part']
def delete(self, **kwargs):
"""Custom delete method for StockItem model."""
if not get_global_setting('STOCK_ALLOW_DELETE_SERIALIZED', cache=False):
if self.serialized:
raise ValidationError(_('Serialized stock items cannot be deleted'))
super().delete(**kwargs)
@staticmethod @staticmethod
def get_api_url(): def get_api_url():
"""Return API url.""" """Return API url."""
+31
View File
@@ -2181,6 +2181,37 @@ class StockItemDeletionTest(StockAPITestCase):
self.assertEqual(StockItem.objects.count(), n) self.assertEqual(StockItem.objects.count(), n)
def test_delete_serialized(self):
"""Test deletion of serialized stock items."""
trackable_part = part.models.Part.objects.create(
name='My part',
description='A trackable part',
trackable=True,
default_location=StockLocation.objects.get(pk=1),
)
stock_item = StockItem.objects.create(
part=trackable_part, quantity=1, serial='12345'
)
set_global_setting('STOCK_ALLOW_DELETE_SERIALIZED', False)
response = self.delete(
reverse('api-stock-detail', kwargs={'pk': stock_item.pk}), expected_code=400
)
self.assertIn('Serialized stock items cannot be deleted', str(response.data))
set_global_setting('STOCK_ALLOW_DELETE_SERIALIZED', True)
response = self.delete(
reverse('api-stock-detail', kwargs={'pk': stock_item.pk}), expected_code=204
)
self.get(
reverse('api-stock-detail', kwargs={'pk': stock_item.pk}), expected_code=404
)
class StockTestResultTest(StockAPITestCase): class StockTestResultTest(StockAPITestCase):
"""Tests for StockTestResult APIs.""" """Tests for StockTestResult APIs."""
@@ -245,6 +245,7 @@ export default function SystemSettings() {
<GlobalSettingList <GlobalSettingList
keys={[ keys={[
'SERIAL_NUMBER_GLOBALLY_UNIQUE', 'SERIAL_NUMBER_GLOBALLY_UNIQUE',
'STOCK_ALLOW_DELETE_SERIALIZED',
'STOCK_DELETE_DEPLETED_DEFAULT', 'STOCK_DELETE_DEPLETED_DEFAULT',
'STOCK_BATCH_CODE_TEMPLATE', 'STOCK_BATCH_CODE_TEMPLATE',
'STOCK_OWNERSHIP_CONTROL', 'STOCK_OWNERSHIP_CONTROL',