diff --git a/src/backend/InvenTree/part/api.py b/src/backend/InvenTree/part/api.py index df5397e98b..6d31392606 100644 --- a/src/backend/InvenTree/part/api.py +++ b/src/backend/InvenTree/part/api.py @@ -283,13 +283,11 @@ class CategoryDetail(CategoryMixin, OutputOptionsMixin, CustomRetrieveUpdateDest def destroy(self, request, *args, **kwargs): """Delete a Part category instance via the API.""" - delete_parts = ( - 'delete_parts' in request.data and request.data['delete_parts'] == '1' - ) - delete_child_categories = ( - 'delete_child_categories' in request.data - and request.data['delete_child_categories'] == '1' + delete_parts = str2bool(request.data.get('delete_parts', False)) + delete_child_categories = str2bool( + request.data.get('delete_child_categories', False) ) + return super().destroy( request, *args, diff --git a/src/backend/InvenTree/stock/api.py b/src/backend/InvenTree/stock/api.py index 11beec18e1..b06151d259 100644 --- a/src/backend/InvenTree/stock/api.py +++ b/src/backend/InvenTree/stock/api.py @@ -431,8 +431,12 @@ class StockLocationDetail( def destroy(self, request, *args, **kwargs): """Delete a Stock location instance via the API.""" - delete_stock_items = str(request.data.get('delete_stock_items', 0)) == '1' - delete_sub_locations = str(request.data.get('delete_sub_locations', 0)) == '1' + delete_stock_items = InvenTree.helpers.str2bool( + request.data.get('delete_stock_items', False) + ) + delete_sub_locations = InvenTree.helpers.str2bool( + request.data.get('delete_sub_locations', False) + ) return super().destroy( request, diff --git a/src/frontend/src/pages/part/CategoryDetail.tsx b/src/frontend/src/pages/part/CategoryDetail.tsx index 81f1f96a55..55b9ce440f 100644 --- a/src/frontend/src/pages/part/CategoryDetail.tsx +++ b/src/frontend/src/pages/part/CategoryDetail.tsx @@ -185,11 +185,11 @@ export default function CategoryDetail() { const deleteOptions = useMemo(() => { return [ { - value: 0, + value: 'false', display_name: t`Move items to parent category` }, { - value: 1, + value: 'true', display_name: t`Delete items` } ]; @@ -204,12 +204,14 @@ export default function CategoryDetail() { label: t`Parts Action`, description: t`Action for parts in this category`, choices: deleteOptions, + required: true, field_type: 'choice' }, delete_child_categories: { label: t`Child Categories Action`, description: t`Action for child categories in this category`, choices: deleteOptions, + required: true, field_type: 'choice' } }, diff --git a/src/frontend/src/pages/stock/LocationDetail.tsx b/src/frontend/src/pages/stock/LocationDetail.tsx index eb844ab928..7018a83c2f 100644 --- a/src/frontend/src/pages/stock/LocationDetail.tsx +++ b/src/frontend/src/pages/stock/LocationDetail.tsx @@ -218,11 +218,11 @@ export default function Stock() { const deleteOptions = useMemo(() => { return [ { - value: 0, + value: 'false', display_name: t`Move items to parent location` }, { - value: 1, + value: 'true', display_name: t`Delete items` } ]; @@ -235,12 +235,14 @@ export default function Stock() { fields: { delete_stock_items: { label: t`Items Action`, + required: true, description: t`Action for stock items in this location`, field_type: 'choice', choices: deleteOptions }, - delete_sub_location: { - label: t`Child Locations Action`, + delete_sub_locations: { + label: t`Locations Action`, + required: true, description: t`Action for child locations in this location`, field_type: 'choice', choices: deleteOptions diff --git a/src/frontend/tests/pages/pui_stock.spec.ts b/src/frontend/tests/pages/pui_stock.spec.ts index 1dbb650aaa..12c8edcebd 100644 --- a/src/frontend/tests/pages/pui_stock.spec.ts +++ b/src/frontend/tests/pages/pui_stock.spec.ts @@ -54,6 +54,67 @@ test('Stock - Location Tree', async ({ browser }) => { await page.getByRole('cell', { name: 'Factory' }).first().waitFor(); }); +test('Stock - Location Delete', async ({ browser }) => { + const page = await doCachedLogin(browser, { + url: 'stock/location/38/sublocations' + }); + + // Create a sub-location + await page + .getByRole('button', { name: 'action-button-add-stock-location' }) + .click(); + await page + .getByRole('textbox', { name: 'text-field-name' }) + .fill('my-location-1'); + await page.getByRole('button', { name: 'Submit' }).click(); + + // Create a secondary sub-location + await loadTab(page, 'Sublocations'); + await page + .getByRole('button', { name: 'action-button-add-stock-location' }) + .click(); + await page + .getByRole('textbox', { name: 'text-field-name' }) + .fill('my-location-2'); + await page.getByRole('button', { name: 'Submit' }).click(); + + // Navigate up to parent + await page.getByRole('link', { name: 'breadcrumb-2-my-location-1' }).click(); + await loadTab(page, 'Sublocations'); + await page + .getByRole('cell', { name: 'my-location-2', exact: true }) + .waitFor(); + + // Delete this location, and all child locations + await page + .locator('div') + .filter({ hasText: /^Stock>PCB Assembler>my-location-1Stock Location$/ }) + .getByLabel('action-menu-location-actions') + .click(); + await page + .getByRole('menuitem', { name: 'action-menu-location-actions-delete' }) + .click(); + + await page + .getByRole('textbox', { name: 'choice-field-delete_stock_items' }) + .click(); + await page + .getByRole('option', { name: 'Move items to parent location' }) + .click(); + + await page + .getByRole('textbox', { name: 'choice-field-delete_sub_locations' }) + .click(); + await page.getByRole('option', { name: 'Delete items' }).click(); + + await page.getByRole('button', { name: 'Delete' }).click(); + + // Confirm we are on the right page + await page.getByText('External PCB assembler').waitFor(); + await loadTab(page, 'Sublocations'); + await page.getByText('No records found').first().waitFor(); +}); + test('Stock - Filters', async ({ browser }) => { const page = await doCachedLogin(browser, { username: 'steven',