2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-18 04:55:44 +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:
Oliver
2025-03-17 23:27:32 +11:00
committed by GitHub
parent 9db5205f79
commit ddc3cd32f5
10 changed files with 136 additions and 22 deletions

View File

@ -43,7 +43,8 @@ export default defineConfig({
INVENTREE_ADMIN_URL: 'test-admin',
INVENTREE_SITE_URL: 'http://localhost:8000',
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/',
reuseExistingServer: !process.env.CI,

View File

@ -213,6 +213,7 @@ export default function SalesOrderShipmentDetail() {
icon: <IconBookmark />,
content: (
<SalesOrderAllocationTable
orderId={shipment.order}
shipmentId={shipment.pk}
showPartInfo
allowEdit={isPending}

View File

@ -172,6 +172,12 @@ export default function Stock() {
icon: <IconInfoCircle />,
content: detailsPanel
},
{
name: 'sublocations',
label: t`Stock Locations`,
icon: <IconSitemap />,
content: <StockLocationTable parentId={id} />
},
{
name: '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',
label: t`Default Parts`,

View File

@ -5,12 +5,15 @@ import { useCallback, useMemo, useState } from 'react';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { YesNoButton } from '../../components/buttons/YesNoButton';
import { ActionDropdown } from '../../components/items/ActionDropdown';
import { ApiIcon } from '../../components/items/ApiIcon';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { partCategoryFields } from '../../forms/PartForms';
import { InvenTreeIcon } from '../../functions/icons';
import {
useBulkEditApiFormModal,
useCreateApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
@ -120,10 +123,38 @@ export function PartCategoryTable({ parentId }: Readonly<{ parentId?: any }>) {
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 can_add = user.hasAddRole(UserRoles.part_category);
const can_edit = user.hasChangeRole(UserRoles.part_category);
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
key='add-part-category'
tooltip={t`Add Part Category`}
@ -131,7 +162,7 @@ export function PartCategoryTable({ parentId }: Readonly<{ parentId?: any }>) {
hidden={!can_add}
/>
];
}, [user]);
}, [user, table.hasSelectedRecords]);
const rowActions = useCallback(
(record: any): RowAction[] => {
@ -154,12 +185,14 @@ export function PartCategoryTable({ parentId }: Readonly<{ parentId?: any }>) {
<>
{newCategory.modal}
{editCategory.modal}
{setParent.modal}
<InvenTreeTable
url={apiUrl(ApiEndpoints.category_list)}
tableState={table}
columns={tableColumns}
props={{
enableDownload: true,
enableSelection: true,
params: {
parent: parentId,
top_level: parentId === undefined ? true : undefined

View File

@ -1,11 +1,15 @@
import { t } from '@lingui/macro';
import { useCallback, useMemo, useState } from 'react';
import { IconTruckDelivery } from '@tabler/icons-react';
import { ActionButton } from '../../components/buttons/ActionButton';
import { formatDate } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { useSalesOrderAllocationFields } from '../../forms/SalesOrderForms';
import {
useBulkEditApiFormModal,
useDeleteApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
@ -244,16 +248,51 @@ export default function SalesOrderAllocationTable({
[allowEdit, user]
);
const tableActions = useMemo(() => {
if (!allowEdit) {
return [];
}
// A subset of the selected allocations, which can be assigned to a shipment
const nonShippedAllocationIds: number[] = useMemo(() => {
// 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 [];
}, [allowEdit, user]);
const setShipment = useBulkEditApiFormModal({
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 (
<>
{setShipment.modal}
{editAllocation.modal}
{deleteAllocation.modal}
<InvenTreeTable
@ -277,9 +316,10 @@ export default function SalesOrderAllocationTable({
enableColumnSwitching: !isSubTable,
enableFilters: !isSubTable,
enableDownload: !isSubTable,
enableSelection: !isSubTable,
minHeight: isSubTable ? 100 : undefined,
rowActions: rowActions,
tableActions: tableActions,
tableActions: isSubTable ? undefined : tableActions,
tableFilters: tableFilters,
modelField: modelField ?? 'order',
modelType: modelTarget ?? ModelType.salesorder

View File

@ -3,12 +3,15 @@ import { Group } from '@mantine/core';
import { useCallback, useMemo, useState } from 'react';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { ActionDropdown } from '../../components/items/ActionDropdown';
import { ApiIcon } from '../../components/items/ApiIcon';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { stockLocationFields } from '../../forms/StockForms';
import { InvenTreeIcon } from '../../functions/icons';
import {
useBulkEditApiFormModal,
useCreateApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
@ -118,10 +121,38 @@ export function StockLocationTable({ parentId }: Readonly<{ parentId?: any }>) {
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 can_add = user.hasAddRole(UserRoles.stock_location);
const can_edit = user.hasChangeRole(UserRoles.stock_location);
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
key='add-stock-location'
tooltip={t`Add Stock Location`}
@ -129,7 +160,7 @@ export function StockLocationTable({ parentId }: Readonly<{ parentId?: any }>) {
hidden={!can_add}
/>
];
}, [user]);
}, [user, table.hasSelectedRecords]);
const rowActions = useCallback(
(record: any): RowAction[] => {
@ -152,6 +183,7 @@ export function StockLocationTable({ parentId }: Readonly<{ parentId?: any }>) {
<>
{newLocation.modal}
{editLocation.modal}
{setParent.modal}
<InvenTreeTable
url={apiUrl(ApiEndpoints.stock_location_list)}
tableState={table}