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

Delete output check (#11881)

* Allow deletion of serialized build output

- If calling "delete_output" on a BuildOrder
- Only will delete an in_production output
- Ignore the global setting in this case

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

* Add unit test

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

---------

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Oliver
2026-05-06 16:14:49 +10:00
committed by GitHub
parent aae97eeb40
commit 81fbf52c60
3 changed files with 51 additions and 4 deletions
+3 -1
View File
@@ -1033,7 +1033,9 @@ class Build(
self.deallocate_stock(output=output) self.deallocate_stock(output=output)
# Remove the build output from the database # Remove the build output from the database
output.delete() # This is a special case where serialized stock can be deleted,
# independedent of the global setting which normally prevents deletion of serialized stock items
output.delete(ignore_serial_check=True)
@transaction.atomic @transaction.atomic
def trim_allocated_stock(self): def trim_allocated_stock(self):
+39
View File
@@ -9,6 +9,7 @@ from rest_framework import status
from build.models import Build, BuildItem, BuildLine from build.models import Build, BuildItem, BuildLine
from build.status_codes import BuildStatus from build.status_codes import BuildStatus
from common.settings import set_global_setting
from InvenTree.unit_test import InvenTreeAPITestCase from InvenTree.unit_test import InvenTreeAPITestCase
from part.models import BomItem, Part from part.models import BomItem, Part
from stock.models import StockItem from stock.models import StockItem
@@ -1548,6 +1549,44 @@ class BuildOutputScrapTest(BuildAPITest):
self.assertFalse(completed_output.is_building) self.assertFalse(completed_output.is_building)
class BuildOutputCancelTest(BuildAPITest):
"""Test cancellation of build outputs."""
def test_cancel_output(self):
"""Test cancellation of a build output."""
build = Build.objects.get(pk=1)
build.part.trackable = True
build.part.save()
N = build.build_outputs.count()
# Create outputs
outputs = build.create_build_output(2, serials=['101', '202'])
self.assertEqual(outputs.count(), 2)
self.assertEqual(build.build_outputs.count(), N + 2)
output_ids = list(outputs.values_list('pk', flat=True))
# Let's cancel one of the outputs
set_global_setting('STOCK_ALLOW_DELETE_SERIALIZED', True)
url = reverse('api-build-output-delete', kwargs={'pk': build.pk})
self.post(url, data={'outputs': [{'output': output_ids[0]}]}, expected_code=201)
# Prevent deletion of serialized stock items, and try again
# Note that this should still succeed, independent of the global setting
set_global_setting('STOCK_ALLOW_DELETE_SERIALIZED', False)
self.post(url, data={'outputs': [{'output': output_ids[1]}]}, expected_code=201)
# The outputs should have been scrapped
self.assertEqual(build.build_outputs.count(), N)
for pk in output_ids:
self.assertFalse(StockItem.objects.filter(pk=pk).exists())
self.get(reverse('api-stock-detail', kwargs={'pk': pk}), expected_code=404)
class BuildLineTests(BuildAPITest): class BuildLineTests(BuildAPITest):
"""Unit tests for the BuildLine API endpoints.""" """Unit tests for the BuildLine API endpoints."""
+9 -3
View File
@@ -449,9 +449,15 @@ class StockItem(
order_insertion_by = ['part'] order_insertion_by = ['part']
def delete(self, **kwargs): def delete(self, ignore_serial_check: bool = False, **kwargs):
"""Custom delete method for StockItem model.""" """Custom delete method for StockItem model.
if not get_global_setting('STOCK_ALLOW_DELETE_SERIALIZED', cache=False):
Arguments:
ignore_serial_check: If True, allow deletion of serialized stock items regardless of global setting
"""
if not ignore_serial_check and not get_global_setting(
'STOCK_ALLOW_DELETE_SERIALIZED', cache=False
):
if self.serialized: if self.serialized:
raise ValidationError(_('Serialized stock items cannot be deleted')) raise ValidationError(_('Serialized stock items cannot be deleted'))