2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 11:36:44 +00:00

[PUI] BOM table updates (#7561)

* Update BOM table for PUI

- Display "validated" column
- Allow ordering by "validated" column

* Update table rowActions

* Add row action to validate BOM line

* Enable bulk deletion of BOM items
This commit is contained in:
Oliver 2024-07-05 16:39:54 +10:00 committed by GitHub
parent 13dbfd0b14
commit 58f12f5ce5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 56 additions and 41 deletions

View File

@ -1872,6 +1872,7 @@ class BomList(BomMixin, ListCreateDestroyAPIView):
'inherited', 'inherited',
'optional', 'optional',
'consumable', 'consumable',
'validated',
'pricing_min', 'pricing_min',
'pricing_max', 'pricing_max',
'pricing_min_total', 'pricing_min_total',

View File

@ -1458,7 +1458,7 @@ class BomItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
- This saves a bunch of database requests - This saves a bunch of database requests
""" """
part_detail = kwargs.pop('part_detail', False) part_detail = kwargs.pop('part_detail', False)
sub_part_detail = kwargs.pop('sub_part_detail', False) sub_part_detail = kwargs.pop('sub_part_detail', True)
pricing = kwargs.pop('pricing', True) pricing = kwargs.pop('pricing', True)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

@ -60,6 +60,7 @@ export enum ApiEndpoints {
build_line_list = 'build/line/', build_line_list = 'build/line/',
bom_list = 'bom/', bom_list = 'bom/',
bom_item_validate = 'bom/:id/validate/',
// Part API endpoints // Part API endpoints
part_list = 'part/', part_list = 'part/',

View File

@ -478,8 +478,8 @@ export function InvenTreeTable<T = any>({
}, [data]); }, [data]);
// Callback function to delete the selected records in the table // Callback function to delete the selected records in the table
const deleteSelectedRecords = useCallback(() => { const deleteSelectedRecords = useCallback((ids: number[]) => {
if (tableState.selectedRecords.length == 0) { if (ids.length == 0) {
// Ignore if no records are selected // Ignore if no records are selected
return; return;
} }
@ -502,15 +502,10 @@ export function InvenTreeTable<T = any>({
color: 'red' color: 'red'
}, },
onConfirm: () => { onConfirm: () => {
// Delete the selected records
let selection = tableState.selectedRecords.map(
(record) => record.pk ?? record.id
);
api api
.delete(url, { .delete(url, {
data: { data: {
items: selection items: ids
} }
}) })
.then((_response) => { .then((_response) => {
@ -535,7 +530,7 @@ export function InvenTreeTable<T = any>({
}); });
} }
}); });
}, [tableState.selectedRecords]); }, []);
// Callback when a row is clicked // Callback when a row is clicked
const handleRowClick = useCallback( const handleRowClick = useCallback(
@ -609,7 +604,7 @@ export function InvenTreeTable<T = any>({
icon={<IconTrash />} icon={<IconTrash />}
color="red" color="red"
tooltip={t`Delete selected records`} tooltip={t`Delete selected records`}
onClick={deleteSelectedRecords} onClick={() => deleteSelectedRecords(tableState.selectedIds)}
/> />
)} )}
{tableProps.tableActions?.map((group, idx) => ( {tableProps.tableActions?.map((group, idx) => (

View File

@ -1,5 +1,6 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Group, Text } from '@mantine/core'; import { Group, Text } from '@mantine/core';
import { showNotification } from '@mantine/notifications';
import { import {
IconArrowRight, IconArrowRight,
IconCircleCheck, IconCircleCheck,
@ -8,6 +9,7 @@ import {
import { ReactNode, useCallback, useMemo, useState } from 'react'; import { ReactNode, useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
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 { Thumbnail } from '../../components/images/Thumbnail'; import { Thumbnail } from '../../components/images/Thumbnail';
@ -33,7 +35,7 @@ import {
} from '../ColumnRenderers'; } from '../ColumnRenderers';
import { TableFilter } from '../Filter'; import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable'; import { InvenTreeTable } from '../InvenTreeTable';
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions'; import { RowDeleteAction, RowEditAction } from '../RowActions';
import { TableHoverCard } from '../TableHoverCard'; import { TableHoverCard } from '../TableHoverCard';
// Calculate the total stock quantity available for a given BomItem // Calculate the total stock quantity available for a given BomItem
@ -146,6 +148,9 @@ export function BomTable({
// TODO: Custom renderer for this column // TODO: Custom renderer for this column
// TODO: See bom.js for existing implementation // TODO: See bom.js for existing implementation
}), }),
BooleanColumn({
accessor: 'validated'
}),
{ {
accessor: 'price_range', accessor: 'price_range',
title: t`Unit Price`, title: t`Unit Price`,
@ -339,6 +344,29 @@ export function BomTable({
table: table table: table
}); });
const validateBomItem = useCallback((record: any) => {
const url = apiUrl(ApiEndpoints.bom_item_validate, record.pk);
api
.patch(url, { valid: true })
.then((_response) => {
showNotification({
title: t`Success`,
message: t`BOM item validated`,
color: 'green'
});
table.refreshTable();
})
.catch((_error) => {
showNotification({
title: t`Error`,
message: t`Failed to validate BOM item`,
color: 'red'
});
});
}, []);
const rowActions = useCallback( const rowActions = useCallback(
(record: any) => { (record: any) => {
// If this BOM item is defined for a *different* parent, then it cannot be edited // If this BOM item is defined for a *different* parent, then it cannot be edited
@ -352,37 +380,27 @@ export function BomTable({
]; ];
} }
let actions: RowAction[] = []; return [
{
// TODO: Enable BomItem validation title: t`Validate BOM Line`,
actions.push({ color: 'green',
title: t`Validate BOM line`, hidden: record.validated || !user.hasChangeRole(UserRoles.part),
color: 'green', icon: <IconCircleCheck />,
hidden: record.validated || !user.hasChangeRole(UserRoles.part), onClick: () => validateBomItem(record)
icon: <IconCircleCheck /> },
});
// TODO: Enable editing of substitutes
actions.push({
title: t`Edit Substitutes`,
color: 'blue',
hidden: !user.hasChangeRole(UserRoles.part),
icon: <IconSwitch3 />
});
// Action on edit
actions.push(
RowEditAction({ RowEditAction({
hidden: !user.hasChangeRole(UserRoles.part), hidden: !user.hasChangeRole(UserRoles.part),
onClick: () => { onClick: () => {
setSelectedBomItem(record.pk); setSelectedBomItem(record.pk);
editBomItem.open(); editBomItem.open();
} }
}) }),
); {
title: t`Edit Substitutes`,
// Action on delete color: 'blue',
actions.push( hidden: !user.hasChangeRole(UserRoles.part),
icon: <IconSwitch3 />
},
RowDeleteAction({ RowDeleteAction({
hidden: !user.hasDeleteRole(UserRoles.part), hidden: !user.hasDeleteRole(UserRoles.part),
onClick: () => { onClick: () => {
@ -390,9 +408,7 @@ export function BomTable({
deleteBomItem.open(); deleteBomItem.open();
} }
}) })
); ];
return actions;
}, },
[partId, user] [partId, user]
); );
@ -427,7 +443,9 @@ export function BomTable({
tableFilters: tableFilters, tableFilters: tableFilters,
modelType: ModelType.part, modelType: ModelType.part,
modelField: 'sub_part', modelField: 'sub_part',
rowActions: rowActions rowActions: rowActions,
enableSelection: true,
enableBulkDelete: true
}} }}
/> />
</> </>