2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-05-17 23:08:28 +00:00

[bug] Bulk edit fix (#11945)

* Prevent editing of items which do not point directly to the part

* Allow table to define which records can be selected
This commit is contained in:
Oliver
2026-05-15 00:24:22 +10:00
committed by GitHub
parent 701a788a6e
commit c931ab6815
3 changed files with 30 additions and 8 deletions
+2
View File
@@ -173,6 +173,7 @@ export type RowViewProps = RowAction & RowModelProps;
* @param barcodeActions : any[] - List of barcode actions * @param barcodeActions : any[] - List of barcode actions
* @param tableFilters : TableFilter[] - List of custom filters * @param tableFilters : TableFilter[] - List of custom filters
* @param tableActions : any[] - List of custom action groups * @param tableActions : any[] - List of custom action groups
* @param isRecordSelectable : (record: any, index: number) => boolean - Callback function to determine if a row is selectable
* @param detailAction: boolean - Enable detail action for each row (default = true) * @param detailAction: boolean - Enable detail action for each row (default = true)
* @param dataFormatter : (data: any) => any - Callback function to reformat data returned by server (if not in default format) * @param dataFormatter : (data: any) => any - Callback function to reformat data returned by server (if not in default format)
* @param rowActions : (record: any) => RowAction[] - Callback function to generate row actions * @param rowActions : (record: any) => RowAction[] - Callback function to generate row actions
@@ -203,6 +204,7 @@ export type InvenTreeTableProps<T = any> = {
barcodeActions?: React.ReactNode[]; barcodeActions?: React.ReactNode[];
tableFilters?: TableFilter[]; tableFilters?: TableFilter[];
tableActions?: React.ReactNode[]; tableActions?: React.ReactNode[];
isRecordSelectable?: (record: T, index: number) => boolean;
rowExpansion?: DataTableRowExpansionProps<T>; rowExpansion?: DataTableRowExpansionProps<T>;
dataFormatter?: (data: any) => any; dataFormatter?: (data: any) => any;
rowActions?: (record: T) => RowAction[]; rowActions?: (record: T) => RowAction[];
@@ -860,6 +860,7 @@ export function InvenTreeTableInternal<T extends Record<string, any>>({
onSelectedRecordsChange={ onSelectedRecordsChange={
enableSelection ? onSelectedRecordsChange : undefined enableSelection ? onSelectedRecordsChange : undefined
} }
isRecordSelectable={tableProps.isRecordSelectable}
rowExpansion={rowExpansion} rowExpansion={rowExpansion}
fetching={isFetching} fetching={isFetching}
noRecordsText={missingRecordsText} noRecordsText={missingRecordsText}
+27 -8
View File
@@ -9,7 +9,7 @@ import useTable from '@lib/hooks/UseTable';
import { ActionButton, RowEditAction, UserRoles } from '@lib/index'; import { ActionButton, RowEditAction, UserRoles } from '@lib/index';
import type { TableFilter } from '@lib/types/Filters'; import type { TableFilter } from '@lib/types/Filters';
import type { RowAction, TableColumn } from '@lib/types/Tables'; import type { RowAction, TableColumn } from '@lib/types/Tables';
import { IconReplace } from '@tabler/icons-react'; import { IconExclamationCircle, IconReplace } from '@tabler/icons-react';
import { formatDecimal } from '../../defaults/formatters'; import { formatDecimal } from '../../defaults/formatters';
import { bomItemFields } from '../../forms/BomForms'; import { bomItemFields } from '../../forms/BomForms';
import { import {
@@ -132,7 +132,10 @@ export function UsedInTable({
return [ return [
RowEditAction({ RowEditAction({
hidden: locked || !user.hasChangeRole(UserRoles.bom), hidden:
locked ||
record.sub_part != partId ||
!user.hasChangeRole(UserRoles.bom),
onClick: () => { onClick: () => {
setSelectedBomItem(record); setSelectedBomItem(record);
editBomItem.open(); editBomItem.open();
@@ -140,14 +143,19 @@ export function UsedInTable({
}) })
]; ];
}, },
[user] [user, partId]
); );
const bulkReplaceParts = useMemo(() => {}, [table.selectedRecords]); const bulkReplaceRecords: any[] = useMemo(() => {
// Only allow replacements of BomItem entries which point to this part
return table.selectedRecords.filter(
(record: any) => record.sub_part === partId
);
}, [table.selectedRecords, partId]);
const bulkReplace = useBulkEditApiFormModal({ const bulkReplace = useBulkEditApiFormModal({
url: ApiEndpoints.bom_list, url: ApiEndpoints.bom_list,
items: table.selectedIds, items: bulkReplaceRecords.map((record: any) => record.pk),
title: t`Replace Component`, title: t`Replace Component`,
submitText: t`Replace`, submitText: t`Replace`,
preFormContent: ( preFormContent: (
@@ -160,8 +168,18 @@ export function UsedInTable({
> >
<Text>{t`This action cannot be easily undone, so please ensure you have selected the correct assemblies.`}</Text> <Text>{t`This action cannot be easily undone, so please ensure you have selected the correct assemblies.`}</Text>
</Alert> </Alert>
<Text>{t`The selected assemblies will be updated with the new component.`}</Text> {bulkReplaceRecords.length ? (
{table.selectedRecords.map((record: any) => { <Text>{t`The selected assemblies will be updated with the new component.`}</Text>
) : (
<Alert
color='red'
icon={<IconExclamationCircle />}
title={t`No valid items selected`}
>
<Text>{t`Please select one or more valid assemblies to replace the component.`}</Text>
</Alert>
)}
{bulkReplaceRecords?.map((record: any) => {
return <RenderPartColumn part={record.part_detail} key={record.pk} />; return <RenderPartColumn part={record.part_detail} key={record.pk} />;
})} })}
<Divider /> <Divider />
@@ -212,7 +230,8 @@ export function UsedInTable({
modelType: ModelType.part, modelType: ModelType.part,
modelField: 'part', modelField: 'part',
tableActions: tableActions, tableActions: tableActions,
tableFilters: tableFilters tableFilters: tableFilters,
isRecordSelectable: (record: any) => record.sub_part === partId
}} }}
/> />
</> </>