From 29fa5cfafaf0d0ef69feb55376f920d08edc2823 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 10 May 2024 12:04:26 +1000 Subject: [PATCH] Transfer out of stock items (#7194) * Use new setting to determine if item can be moved * Add new setting to front-end * Invert double inversion * Prevent empty stock tracking entry * Updated unit tests * Fix rendering of FailedTasksTable --- src/backend/InvenTree/common/models.py | 8 ++++++++ src/backend/InvenTree/stock/models.py | 14 +++++++++++++- src/backend/InvenTree/stock/test_api.py | 16 ++++++++++++++++ .../templates/InvenTree/settings/stock.html | 1 + .../Settings/AdminCenter/TaskManagementPanel.tsx | 6 +++--- .../src/pages/Index/Settings/SystemSettings.tsx | 1 + .../src/tables/settings/FailedTasksTable.tsx | 8 ++++++-- 7 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py index 514a573a38..76dccae4d4 100644 --- a/src/backend/InvenTree/common/models.py +++ b/src/backend/InvenTree/common/models.py @@ -1781,6 +1781,14 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'default': True, 'validator': bool, }, + 'STOCK_ALLOW_OUT_OF_STOCK_TRANSFER': { + 'name': _('Allow Out of Stock Transfer'), + 'description': _( + 'Allow stock items which are not in stock to be transferred between stock locations' + ), + 'default': False, + 'validator': bool, + }, 'BUILDORDER_REFERENCE_PATTERN': { 'name': _('Build Order Reference Pattern'), 'description': _( diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index 2d5cfaeaff..2bfcbc47a1 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -1419,6 +1419,14 @@ class StockItem( if deltas is None: deltas = {} + # Prevent empty entry + if ( + entry_type == StockHistoryCode.STOCK_UPDATE + and len(deltas) == 0 + and not notes + ): + return + # Has a location been specified? location = kwargs.get('location', None) @@ -1866,7 +1874,11 @@ class StockItem( except InvalidOperation: return False - if not self.in_stock: + allow_out_of_stock_transfer = common.models.InvenTreeSetting.get_setting( + 'STOCK_ALLOW_OUT_OF_STOCK_TRANSFER', backup_value=False, cache=False + ) + + if not allow_out_of_stock_transfer and not self.in_stock: raise ValidationError(_('StockItem cannot be moved as it is not in stock')) if quantity <= 0: diff --git a/src/backend/InvenTree/stock/test_api.py b/src/backend/InvenTree/stock/test_api.py index eff284ad89..593f0e0bc9 100644 --- a/src/backend/InvenTree/stock/test_api.py +++ b/src/backend/InvenTree/stock/test_api.py @@ -1470,6 +1470,14 @@ class StocktakeTest(StockAPITestCase): def test_transfer(self): """Test stock transfers.""" + stock_item = StockItem.objects.get(pk=1234) + + # Mark this stock item as "quarantined" (cannot be moved) + stock_item.status = StockStatus.QUARANTINED.value + stock_item.save() + + InvenTreeSetting.set_setting('STOCK_ALLOW_OUT_OF_STOCK_TRANSFER', False) + data = { 'items': [{'pk': 1234, 'quantity': 10}], 'location': 1, @@ -1478,6 +1486,14 @@ class StocktakeTest(StockAPITestCase): url = reverse('api-stock-transfer') + # First attempt should *fail* - stock item is quarantined + response = self.post(url, data, expected_code=400) + + self.assertIn('cannot be moved as it is not in stock', str(response.data)) + + # Now, allow transfer of "out of stock" items + InvenTreeSetting.set_setting('STOCK_ALLOW_OUT_OF_STOCK_TRANSFER', True) + # This should succeed response = self.post(url, data, expected_code=201) diff --git a/src/backend/InvenTree/templates/InvenTree/settings/stock.html b/src/backend/InvenTree/templates/InvenTree/settings/stock.html index 9ccca21af0..89dd5e95b9 100644 --- a/src/backend/InvenTree/templates/InvenTree/settings/stock.html +++ b/src/backend/InvenTree/templates/InvenTree/settings/stock.html @@ -23,6 +23,7 @@ {% include "InvenTree/settings/setting.html" with key="STOCK_LOCATION_DEFAULT_ICON" icon="fa-icons" %} {% include "InvenTree/settings/setting.html" with key="STOCK_SHOW_INSTALLED_ITEMS" icon="fa-sitemap" %} {% include "InvenTree/settings/setting.html" with key="STOCK_ENFORCE_BOM_INSTALLATION" icon="fa-check-circle" %} + {% include "InvenTree/settings/setting.html" with key="STOCK_ALLOW_OUT_OF_STOCK_TRANSFER" icon="fa-dolly" %} {% include "InvenTree/settings/setting.html" with key="TEST_STATION_DATA" icon="fa-network-wired" %} diff --git a/src/frontend/src/pages/Index/Settings/AdminCenter/TaskManagementPanel.tsx b/src/frontend/src/pages/Index/Settings/AdminCenter/TaskManagementPanel.tsx index d8f5213fe0..ab68858d14 100644 --- a/src/frontend/src/pages/Index/Settings/AdminCenter/TaskManagementPanel.tsx +++ b/src/frontend/src/pages/Index/Settings/AdminCenter/TaskManagementPanel.tsx @@ -20,7 +20,7 @@ const FailedTasksTable = Loadable( export default function TaskManagementPanel() { return ( - + {t`Pending Tasks`} @@ -28,7 +28,7 @@ export default function TaskManagementPanel() { - + {t`Scheduled Tasks`} @@ -36,7 +36,7 @@ export default function TaskManagementPanel() { - + {t`Failed Tasks`} diff --git a/src/frontend/src/pages/Index/Settings/SystemSettings.tsx b/src/frontend/src/pages/Index/Settings/SystemSettings.tsx index 40d39e5897..3208caaa09 100644 --- a/src/frontend/src/pages/Index/Settings/SystemSettings.tsx +++ b/src/frontend/src/pages/Index/Settings/SystemSettings.tsx @@ -212,6 +212,7 @@ export default function SystemSettings() { 'STOCK_LOCATION_DEFAULT_ICON', 'STOCK_SHOW_INSTALLED_ITEMS', 'STOCK_ENFORCE_BOM_INSTALLATION', + 'STOCK_ALLOW_OUT_OF_STOCK_TRANSFER', 'TEST_STATION_DATA' ]} /> diff --git a/src/frontend/src/tables/settings/FailedTasksTable.tsx b/src/frontend/src/tables/settings/FailedTasksTable.tsx index ec7a636f36..13f740a64f 100644 --- a/src/frontend/src/tables/settings/FailedTasksTable.tsx +++ b/src/frontend/src/tables/settings/FailedTasksTable.tsx @@ -57,8 +57,12 @@ export default function FailedTasksTable() { title={{t`Error Details`}} onClose={close} > - {error.split('\n').map((line: string) => { - return {line}; + {error.split('\n').map((line: string, index: number) => { + return ( + + {line} + + ); })}