mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 07:05:41 +00:00 
			
		
		
		
	[UI] Bulk edit actions (#9320)
* Allow bulk selection of sales order shipment * Tweaks * Support bulk-edit for location parent and category parent * Allow more login attempts for playwright
This commit is contained in:
		@@ -1,13 +1,18 @@
 | 
				
			|||||||
"""InvenTree API version information."""
 | 
					"""InvenTree API version information."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# InvenTree API version
 | 
					# InvenTree API version
 | 
				
			||||||
INVENTREE_API_VERSION = 323
 | 
					INVENTREE_API_VERSION = 324
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
 | 
					"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
INVENTREE_API_TEXT = """
 | 
					INVENTREE_API_TEXT = """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					v324 - 2025-03-17 : https://github.com/inventree/InvenTree/pull/9320
 | 
				
			||||||
 | 
					    - Adds BulkUpdate support for the SalesOrderAllocation model
 | 
				
			||||||
 | 
					    - Adds BulkUpdate support for the PartCategory model
 | 
				
			||||||
 | 
					    - Adds BulkUpdate support for the StockLocation model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
v323 - 2025-03-17 : https://github.com/inventree/InvenTree/pull/9313
 | 
					v323 - 2025-03-17 : https://github.com/inventree/InvenTree/pull/9313
 | 
				
			||||||
    - Adds BulkUpdate support to the Part API endpoint
 | 
					    - Adds BulkUpdate support to the Part API endpoint
 | 
				
			||||||
    - Remove legacy API endpoint to set part category for multiple parts
 | 
					    - Remove legacy API endpoint to set part category for multiple parts
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,7 @@ import common.settings
 | 
				
			|||||||
import company.models
 | 
					import company.models
 | 
				
			||||||
from generic.states.api import StatusView
 | 
					from generic.states.api import StatusView
 | 
				
			||||||
from importer.mixins import DataExportViewMixin
 | 
					from importer.mixins import DataExportViewMixin
 | 
				
			||||||
from InvenTree.api import ListCreateDestroyAPIView, MetadataView
 | 
					from InvenTree.api import BulkUpdateMixin, ListCreateDestroyAPIView, MetadataView
 | 
				
			||||||
from InvenTree.filters import (
 | 
					from InvenTree.filters import (
 | 
				
			||||||
    SEARCH_ORDER_FILTER,
 | 
					    SEARCH_ORDER_FILTER,
 | 
				
			||||||
    SEARCH_ORDER_FILTER_ALIAS,
 | 
					    SEARCH_ORDER_FILTER_ALIAS,
 | 
				
			||||||
@@ -1178,7 +1178,7 @@ class SalesOrderAllocationMixin:
 | 
				
			|||||||
        return queryset
 | 
					        return queryset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SalesOrderAllocationList(SalesOrderAllocationMixin, ListAPI):
 | 
					class SalesOrderAllocationList(SalesOrderAllocationMixin, BulkUpdateMixin, ListAPI):
 | 
				
			||||||
    """API endpoint for listing SalesOrderAllocation objects."""
 | 
					    """API endpoint for listing SalesOrderAllocation objects."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    filterset_class = SalesOrderAllocationFilter
 | 
					    filterset_class = SalesOrderAllocationFilter
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -226,7 +226,7 @@ class CategoryFilter(rest_filters.FilterSet):
 | 
				
			|||||||
        return queryset
 | 
					        return queryset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CategoryList(CategoryMixin, DataExportViewMixin, ListCreateAPI):
 | 
					class CategoryList(CategoryMixin, BulkUpdateMixin, DataExportViewMixin, ListCreateAPI):
 | 
				
			||||||
    """API endpoint for accessing a list of PartCategory objects.
 | 
					    """API endpoint for accessing a list of PartCategory objects.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - GET: Return a list of PartCategory objects
 | 
					    - GET: Return a list of PartCategory objects
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,7 @@ from company.models import Company, SupplierPart
 | 
				
			|||||||
from company.serializers import CompanySerializer
 | 
					from company.serializers import CompanySerializer
 | 
				
			||||||
from generic.states.api import StatusView
 | 
					from generic.states.api import StatusView
 | 
				
			||||||
from importer.mixins import DataExportViewMixin
 | 
					from importer.mixins import DataExportViewMixin
 | 
				
			||||||
from InvenTree.api import ListCreateDestroyAPIView, MetadataView
 | 
					from InvenTree.api import BulkUpdateMixin, ListCreateDestroyAPIView, MetadataView
 | 
				
			||||||
from InvenTree.filters import (
 | 
					from InvenTree.filters import (
 | 
				
			||||||
    ORDER_FILTER_ALIAS,
 | 
					    ORDER_FILTER_ALIAS,
 | 
				
			||||||
    SEARCH_ORDER_FILTER,
 | 
					    SEARCH_ORDER_FILTER,
 | 
				
			||||||
@@ -365,7 +365,9 @@ class StockLocationMixin:
 | 
				
			|||||||
        return queryset
 | 
					        return queryset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StockLocationList(DataExportViewMixin, StockLocationMixin, ListCreateAPI):
 | 
					class StockLocationList(
 | 
				
			||||||
 | 
					    DataExportViewMixin, BulkUpdateMixin, StockLocationMixin, ListCreateAPI
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
    """API endpoint for list view of StockLocation objects.
 | 
					    """API endpoint for list view of StockLocation objects.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - GET: Return list of StockLocation objects
 | 
					    - GET: Return list of StockLocation objects
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,7 +43,8 @@ export default defineConfig({
 | 
				
			|||||||
        INVENTREE_ADMIN_URL: 'test-admin',
 | 
					        INVENTREE_ADMIN_URL: 'test-admin',
 | 
				
			||||||
        INVENTREE_SITE_URL: 'http://localhost:8000',
 | 
					        INVENTREE_SITE_URL: 'http://localhost:8000',
 | 
				
			||||||
        INVENTREE_CORS_ORIGIN_ALLOW_ALL: 'True',
 | 
					        INVENTREE_CORS_ORIGIN_ALLOW_ALL: 'True',
 | 
				
			||||||
        INVENTREE_COOKIE_SAMESITE: 'Lax'
 | 
					        INVENTREE_COOKIE_SAMESITE: 'Lax',
 | 
				
			||||||
 | 
					        INVENTREE_LOGIN_ATTEMPTS: '100'
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      url: 'http://127.0.0.1:8000/api/',
 | 
					      url: 'http://127.0.0.1:8000/api/',
 | 
				
			||||||
      reuseExistingServer: !process.env.CI,
 | 
					      reuseExistingServer: !process.env.CI,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -213,6 +213,7 @@ export default function SalesOrderShipmentDetail() {
 | 
				
			|||||||
        icon: <IconBookmark />,
 | 
					        icon: <IconBookmark />,
 | 
				
			||||||
        content: (
 | 
					        content: (
 | 
				
			||||||
          <SalesOrderAllocationTable
 | 
					          <SalesOrderAllocationTable
 | 
				
			||||||
 | 
					            orderId={shipment.order}
 | 
				
			||||||
            shipmentId={shipment.pk}
 | 
					            shipmentId={shipment.pk}
 | 
				
			||||||
            showPartInfo
 | 
					            showPartInfo
 | 
				
			||||||
            allowEdit={isPending}
 | 
					            allowEdit={isPending}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -172,6 +172,12 @@ export default function Stock() {
 | 
				
			|||||||
        icon: <IconInfoCircle />,
 | 
					        icon: <IconInfoCircle />,
 | 
				
			||||||
        content: detailsPanel
 | 
					        content: detailsPanel
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: 'sublocations',
 | 
				
			||||||
 | 
					        label: t`Stock Locations`,
 | 
				
			||||||
 | 
					        icon: <IconSitemap />,
 | 
				
			||||||
 | 
					        content: <StockLocationTable parentId={id} />
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        name: 'stock-items',
 | 
					        name: 'stock-items',
 | 
				
			||||||
        label: t`Stock Items`,
 | 
					        label: t`Stock Items`,
 | 
				
			||||||
@@ -186,12 +192,6 @@ export default function Stock() {
 | 
				
			|||||||
          />
 | 
					          />
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        name: 'sublocations',
 | 
					 | 
				
			||||||
        label: t`Stock Locations`,
 | 
					 | 
				
			||||||
        icon: <IconSitemap />,
 | 
					 | 
				
			||||||
        content: <StockLocationTable parentId={id} />
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        name: 'default_parts',
 | 
					        name: 'default_parts',
 | 
				
			||||||
        label: t`Default Parts`,
 | 
					        label: t`Default Parts`,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,12 +5,15 @@ import { useCallback, useMemo, useState } from 'react';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
 | 
					import { AddItemButton } from '../../components/buttons/AddItemButton';
 | 
				
			||||||
import { YesNoButton } from '../../components/buttons/YesNoButton';
 | 
					import { YesNoButton } from '../../components/buttons/YesNoButton';
 | 
				
			||||||
 | 
					import { ActionDropdown } from '../../components/items/ActionDropdown';
 | 
				
			||||||
import { ApiIcon } from '../../components/items/ApiIcon';
 | 
					import { ApiIcon } from '../../components/items/ApiIcon';
 | 
				
			||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
 | 
					import { ApiEndpoints } from '../../enums/ApiEndpoints';
 | 
				
			||||||
import { ModelType } from '../../enums/ModelType';
 | 
					import { ModelType } from '../../enums/ModelType';
 | 
				
			||||||
import { UserRoles } from '../../enums/Roles';
 | 
					import { UserRoles } from '../../enums/Roles';
 | 
				
			||||||
import { partCategoryFields } from '../../forms/PartForms';
 | 
					import { partCategoryFields } from '../../forms/PartForms';
 | 
				
			||||||
 | 
					import { InvenTreeIcon } from '../../functions/icons';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  useBulkEditApiFormModal,
 | 
				
			||||||
  useCreateApiFormModal,
 | 
					  useCreateApiFormModal,
 | 
				
			||||||
  useEditApiFormModal
 | 
					  useEditApiFormModal
 | 
				
			||||||
} from '../../hooks/UseForm';
 | 
					} from '../../hooks/UseForm';
 | 
				
			||||||
@@ -120,10 +123,38 @@ export function PartCategoryTable({ parentId }: Readonly<{ parentId?: any }>) {
 | 
				
			|||||||
    onFormSuccess: (record: any) => table.updateRecord(record)
 | 
					    onFormSuccess: (record: any) => table.updateRecord(record)
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const setParent = useBulkEditApiFormModal({
 | 
				
			||||||
 | 
					    url: ApiEndpoints.category_list,
 | 
				
			||||||
 | 
					    items: table.selectedIds,
 | 
				
			||||||
 | 
					    title: t`Set Parent Category`,
 | 
				
			||||||
 | 
					    fields: {
 | 
				
			||||||
 | 
					      parent: {}
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    onFormSuccess: table.refreshTable
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const tableActions = useMemo(() => {
 | 
					  const tableActions = useMemo(() => {
 | 
				
			||||||
    const can_add = user.hasAddRole(UserRoles.part_category);
 | 
					    const can_add = user.hasAddRole(UserRoles.part_category);
 | 
				
			||||||
 | 
					    const can_edit = user.hasChangeRole(UserRoles.part_category);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return [
 | 
					    return [
 | 
				
			||||||
 | 
					      <ActionDropdown
 | 
				
			||||||
 | 
					        tooltip={t`Category Actions`}
 | 
				
			||||||
 | 
					        icon={<InvenTreeIcon icon='category' />}
 | 
				
			||||||
 | 
					        disabled={!table.hasSelectedRecords}
 | 
				
			||||||
 | 
					        actions={[
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            name: t`Set Parent`,
 | 
				
			||||||
 | 
					            icon: <InvenTreeIcon icon='category' />,
 | 
				
			||||||
 | 
					            tooltip: t`Set parent category for the selected items`,
 | 
				
			||||||
 | 
					            hidden: !can_edit,
 | 
				
			||||||
 | 
					            disabled: !table.hasSelectedRecords,
 | 
				
			||||||
 | 
					            onClick: () => {
 | 
				
			||||||
 | 
					              setParent.open();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ]}
 | 
				
			||||||
 | 
					      />,
 | 
				
			||||||
      <AddItemButton
 | 
					      <AddItemButton
 | 
				
			||||||
        key='add-part-category'
 | 
					        key='add-part-category'
 | 
				
			||||||
        tooltip={t`Add Part Category`}
 | 
					        tooltip={t`Add Part Category`}
 | 
				
			||||||
@@ -131,7 +162,7 @@ export function PartCategoryTable({ parentId }: Readonly<{ parentId?: any }>) {
 | 
				
			|||||||
        hidden={!can_add}
 | 
					        hidden={!can_add}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
  }, [user]);
 | 
					  }, [user, table.hasSelectedRecords]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const rowActions = useCallback(
 | 
					  const rowActions = useCallback(
 | 
				
			||||||
    (record: any): RowAction[] => {
 | 
					    (record: any): RowAction[] => {
 | 
				
			||||||
@@ -154,12 +185,14 @@ export function PartCategoryTable({ parentId }: Readonly<{ parentId?: any }>) {
 | 
				
			|||||||
    <>
 | 
					    <>
 | 
				
			||||||
      {newCategory.modal}
 | 
					      {newCategory.modal}
 | 
				
			||||||
      {editCategory.modal}
 | 
					      {editCategory.modal}
 | 
				
			||||||
 | 
					      {setParent.modal}
 | 
				
			||||||
      <InvenTreeTable
 | 
					      <InvenTreeTable
 | 
				
			||||||
        url={apiUrl(ApiEndpoints.category_list)}
 | 
					        url={apiUrl(ApiEndpoints.category_list)}
 | 
				
			||||||
        tableState={table}
 | 
					        tableState={table}
 | 
				
			||||||
        columns={tableColumns}
 | 
					        columns={tableColumns}
 | 
				
			||||||
        props={{
 | 
					        props={{
 | 
				
			||||||
          enableDownload: true,
 | 
					          enableDownload: true,
 | 
				
			||||||
 | 
					          enableSelection: true,
 | 
				
			||||||
          params: {
 | 
					          params: {
 | 
				
			||||||
            parent: parentId,
 | 
					            parent: parentId,
 | 
				
			||||||
            top_level: parentId === undefined ? true : undefined
 | 
					            top_level: parentId === undefined ? true : undefined
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,15 @@
 | 
				
			|||||||
import { t } from '@lingui/macro';
 | 
					import { t } from '@lingui/macro';
 | 
				
			||||||
import { useCallback, useMemo, useState } from 'react';
 | 
					import { useCallback, useMemo, useState } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { IconTruckDelivery } from '@tabler/icons-react';
 | 
				
			||||||
 | 
					import { ActionButton } from '../../components/buttons/ActionButton';
 | 
				
			||||||
import { formatDate } from '../../defaults/formatters';
 | 
					import { formatDate } from '../../defaults/formatters';
 | 
				
			||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
 | 
					import { ApiEndpoints } from '../../enums/ApiEndpoints';
 | 
				
			||||||
import { ModelType } from '../../enums/ModelType';
 | 
					import { ModelType } from '../../enums/ModelType';
 | 
				
			||||||
 | 
					import { UserRoles } from '../../enums/Roles';
 | 
				
			||||||
import { useSalesOrderAllocationFields } from '../../forms/SalesOrderForms';
 | 
					import { useSalesOrderAllocationFields } from '../../forms/SalesOrderForms';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  useBulkEditApiFormModal,
 | 
				
			||||||
  useDeleteApiFormModal,
 | 
					  useDeleteApiFormModal,
 | 
				
			||||||
  useEditApiFormModal
 | 
					  useEditApiFormModal
 | 
				
			||||||
} from '../../hooks/UseForm';
 | 
					} from '../../hooks/UseForm';
 | 
				
			||||||
@@ -244,16 +248,51 @@ export default function SalesOrderAllocationTable({
 | 
				
			|||||||
    [allowEdit, user]
 | 
					    [allowEdit, user]
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const tableActions = useMemo(() => {
 | 
					  // A subset of the selected allocations, which can be assigned to a shipment
 | 
				
			||||||
    if (!allowEdit) {
 | 
					  const nonShippedAllocationIds: number[] = useMemo(() => {
 | 
				
			||||||
      return [];
 | 
					    // Only allow allocations which have not been shipped
 | 
				
			||||||
    }
 | 
					    return (
 | 
				
			||||||
 | 
					      table.selectedRecords?.filter((record) => {
 | 
				
			||||||
 | 
					        return !record.shipment_detail?.shipment_date;
 | 
				
			||||||
 | 
					      }) ?? []
 | 
				
			||||||
 | 
					    ).map((record: any) => record.pk);
 | 
				
			||||||
 | 
					  }, [table.selectedRecords]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return [];
 | 
					  const setShipment = useBulkEditApiFormModal({
 | 
				
			||||||
  }, [allowEdit, user]);
 | 
					    url: ApiEndpoints.sales_order_allocation_list,
 | 
				
			||||||
 | 
					    items: nonShippedAllocationIds,
 | 
				
			||||||
 | 
					    title: t`Assign to Shipment`,
 | 
				
			||||||
 | 
					    fields: {
 | 
				
			||||||
 | 
					      shipment: {
 | 
				
			||||||
 | 
					        filters: {
 | 
				
			||||||
 | 
					          order: orderId,
 | 
				
			||||||
 | 
					          shipped: false
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    onFormSuccess: table.refreshTable
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const tableActions = useMemo(() => {
 | 
				
			||||||
 | 
					    return [
 | 
				
			||||||
 | 
					      <ActionButton
 | 
				
			||||||
 | 
					        tooltip={t`Assign to shipment`}
 | 
				
			||||||
 | 
					        icon={<IconTruckDelivery />}
 | 
				
			||||||
 | 
					        onClick={() => {
 | 
				
			||||||
 | 
					          setShipment.open();
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					        disabled={nonShippedAllocationIds.length == 0}
 | 
				
			||||||
 | 
					        hidden={
 | 
				
			||||||
 | 
					          !orderId || !allowEdit || !user.hasChangeRole(UserRoles.sales_order)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // TODO: Hide if order is already shipped
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					  }, [allowEdit, nonShippedAllocationIds, orderId, user]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
 | 
					      {setShipment.modal}
 | 
				
			||||||
      {editAllocation.modal}
 | 
					      {editAllocation.modal}
 | 
				
			||||||
      {deleteAllocation.modal}
 | 
					      {deleteAllocation.modal}
 | 
				
			||||||
      <InvenTreeTable
 | 
					      <InvenTreeTable
 | 
				
			||||||
@@ -277,9 +316,10 @@ export default function SalesOrderAllocationTable({
 | 
				
			|||||||
          enableColumnSwitching: !isSubTable,
 | 
					          enableColumnSwitching: !isSubTable,
 | 
				
			||||||
          enableFilters: !isSubTable,
 | 
					          enableFilters: !isSubTable,
 | 
				
			||||||
          enableDownload: !isSubTable,
 | 
					          enableDownload: !isSubTable,
 | 
				
			||||||
 | 
					          enableSelection: !isSubTable,
 | 
				
			||||||
          minHeight: isSubTable ? 100 : undefined,
 | 
					          minHeight: isSubTable ? 100 : undefined,
 | 
				
			||||||
          rowActions: rowActions,
 | 
					          rowActions: rowActions,
 | 
				
			||||||
          tableActions: tableActions,
 | 
					          tableActions: isSubTable ? undefined : tableActions,
 | 
				
			||||||
          tableFilters: tableFilters,
 | 
					          tableFilters: tableFilters,
 | 
				
			||||||
          modelField: modelField ?? 'order',
 | 
					          modelField: modelField ?? 'order',
 | 
				
			||||||
          modelType: modelTarget ?? ModelType.salesorder
 | 
					          modelType: modelTarget ?? ModelType.salesorder
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,12 +3,15 @@ import { Group } from '@mantine/core';
 | 
				
			|||||||
import { useCallback, useMemo, useState } from 'react';
 | 
					import { useCallback, useMemo, useState } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
 | 
					import { AddItemButton } from '../../components/buttons/AddItemButton';
 | 
				
			||||||
 | 
					import { ActionDropdown } from '../../components/items/ActionDropdown';
 | 
				
			||||||
import { ApiIcon } from '../../components/items/ApiIcon';
 | 
					import { ApiIcon } from '../../components/items/ApiIcon';
 | 
				
			||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
 | 
					import { ApiEndpoints } from '../../enums/ApiEndpoints';
 | 
				
			||||||
import { ModelType } from '../../enums/ModelType';
 | 
					import { ModelType } from '../../enums/ModelType';
 | 
				
			||||||
import { UserRoles } from '../../enums/Roles';
 | 
					import { UserRoles } from '../../enums/Roles';
 | 
				
			||||||
import { stockLocationFields } from '../../forms/StockForms';
 | 
					import { stockLocationFields } from '../../forms/StockForms';
 | 
				
			||||||
 | 
					import { InvenTreeIcon } from '../../functions/icons';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  useBulkEditApiFormModal,
 | 
				
			||||||
  useCreateApiFormModal,
 | 
					  useCreateApiFormModal,
 | 
				
			||||||
  useEditApiFormModal
 | 
					  useEditApiFormModal
 | 
				
			||||||
} from '../../hooks/UseForm';
 | 
					} from '../../hooks/UseForm';
 | 
				
			||||||
@@ -118,10 +121,38 @@ export function StockLocationTable({ parentId }: Readonly<{ parentId?: any }>) {
 | 
				
			|||||||
    onFormSuccess: (record: any) => table.updateRecord(record)
 | 
					    onFormSuccess: (record: any) => table.updateRecord(record)
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const setParent = useBulkEditApiFormModal({
 | 
				
			||||||
 | 
					    url: ApiEndpoints.stock_location_list,
 | 
				
			||||||
 | 
					    items: table.selectedIds,
 | 
				
			||||||
 | 
					    title: t`Set Parent Location`,
 | 
				
			||||||
 | 
					    fields: {
 | 
				
			||||||
 | 
					      parent: {}
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    onFormSuccess: table.refreshTable
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const tableActions = useMemo(() => {
 | 
					  const tableActions = useMemo(() => {
 | 
				
			||||||
    const can_add = user.hasAddRole(UserRoles.stock_location);
 | 
					    const can_add = user.hasAddRole(UserRoles.stock_location);
 | 
				
			||||||
 | 
					    const can_edit = user.hasChangeRole(UserRoles.stock_location);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return [
 | 
					    return [
 | 
				
			||||||
 | 
					      <ActionDropdown
 | 
				
			||||||
 | 
					        tooltip={t`Location Actions`}
 | 
				
			||||||
 | 
					        icon={<InvenTreeIcon icon='location' />}
 | 
				
			||||||
 | 
					        disabled={!table.hasSelectedRecords}
 | 
				
			||||||
 | 
					        actions={[
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            name: t`Set Parent`,
 | 
				
			||||||
 | 
					            icon: <InvenTreeIcon icon='location' />,
 | 
				
			||||||
 | 
					            tooltip: t`Set parent location for the selected items`,
 | 
				
			||||||
 | 
					            hidden: !can_edit,
 | 
				
			||||||
 | 
					            disabled: !table.hasSelectedRecords,
 | 
				
			||||||
 | 
					            onClick: () => {
 | 
				
			||||||
 | 
					              setParent.open();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ]}
 | 
				
			||||||
 | 
					      />,
 | 
				
			||||||
      <AddItemButton
 | 
					      <AddItemButton
 | 
				
			||||||
        key='add-stock-location'
 | 
					        key='add-stock-location'
 | 
				
			||||||
        tooltip={t`Add Stock Location`}
 | 
					        tooltip={t`Add Stock Location`}
 | 
				
			||||||
@@ -129,7 +160,7 @@ export function StockLocationTable({ parentId }: Readonly<{ parentId?: any }>) {
 | 
				
			|||||||
        hidden={!can_add}
 | 
					        hidden={!can_add}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
  }, [user]);
 | 
					  }, [user, table.hasSelectedRecords]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const rowActions = useCallback(
 | 
					  const rowActions = useCallback(
 | 
				
			||||||
    (record: any): RowAction[] => {
 | 
					    (record: any): RowAction[] => {
 | 
				
			||||||
@@ -152,6 +183,7 @@ export function StockLocationTable({ parentId }: Readonly<{ parentId?: any }>) {
 | 
				
			|||||||
    <>
 | 
					    <>
 | 
				
			||||||
      {newLocation.modal}
 | 
					      {newLocation.modal}
 | 
				
			||||||
      {editLocation.modal}
 | 
					      {editLocation.modal}
 | 
				
			||||||
 | 
					      {setParent.modal}
 | 
				
			||||||
      <InvenTreeTable
 | 
					      <InvenTreeTable
 | 
				
			||||||
        url={apiUrl(ApiEndpoints.stock_location_list)}
 | 
					        url={apiUrl(ApiEndpoints.stock_location_list)}
 | 
				
			||||||
        tableState={table}
 | 
					        tableState={table}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user