From 58f12f5ce5f93d1d52bdffdaa8db68d013c3ab8e Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 5 Jul 2024 16:39:54 +1000 Subject: [PATCH] [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 --- src/backend/InvenTree/part/api.py | 1 + src/backend/InvenTree/part/serializers.py | 2 +- src/frontend/src/enums/ApiEndpoints.tsx | 1 + src/frontend/src/tables/InvenTreeTable.tsx | 15 ++--- src/frontend/src/tables/bom/BomTable.tsx | 78 +++++++++++++--------- 5 files changed, 56 insertions(+), 41 deletions(-) diff --git a/src/backend/InvenTree/part/api.py b/src/backend/InvenTree/part/api.py index 9634e5071d..1907f07ce4 100644 --- a/src/backend/InvenTree/part/api.py +++ b/src/backend/InvenTree/part/api.py @@ -1872,6 +1872,7 @@ class BomList(BomMixin, ListCreateDestroyAPIView): 'inherited', 'optional', 'consumable', + 'validated', 'pricing_min', 'pricing_max', 'pricing_min_total', diff --git a/src/backend/InvenTree/part/serializers.py b/src/backend/InvenTree/part/serializers.py index 90af41890e..1cc168421d 100644 --- a/src/backend/InvenTree/part/serializers.py +++ b/src/backend/InvenTree/part/serializers.py @@ -1458,7 +1458,7 @@ class BomItemSerializer(InvenTree.serializers.InvenTreeModelSerializer): - This saves a bunch of database requests """ 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) super().__init__(*args, **kwargs) diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index 2775fa1845..3713676c25 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -60,6 +60,7 @@ export enum ApiEndpoints { build_line_list = 'build/line/', bom_list = 'bom/', + bom_item_validate = 'bom/:id/validate/', // Part API endpoints part_list = 'part/', diff --git a/src/frontend/src/tables/InvenTreeTable.tsx b/src/frontend/src/tables/InvenTreeTable.tsx index ef77ec65c5..1c445279ca 100644 --- a/src/frontend/src/tables/InvenTreeTable.tsx +++ b/src/frontend/src/tables/InvenTreeTable.tsx @@ -478,8 +478,8 @@ export function InvenTreeTable({ }, [data]); // Callback function to delete the selected records in the table - const deleteSelectedRecords = useCallback(() => { - if (tableState.selectedRecords.length == 0) { + const deleteSelectedRecords = useCallback((ids: number[]) => { + if (ids.length == 0) { // Ignore if no records are selected return; } @@ -502,15 +502,10 @@ export function InvenTreeTable({ color: 'red' }, onConfirm: () => { - // Delete the selected records - let selection = tableState.selectedRecords.map( - (record) => record.pk ?? record.id - ); - api .delete(url, { data: { - items: selection + items: ids } }) .then((_response) => { @@ -535,7 +530,7 @@ export function InvenTreeTable({ }); } }); - }, [tableState.selectedRecords]); + }, []); // Callback when a row is clicked const handleRowClick = useCallback( @@ -609,7 +604,7 @@ export function InvenTreeTable({ icon={} color="red" tooltip={t`Delete selected records`} - onClick={deleteSelectedRecords} + onClick={() => deleteSelectedRecords(tableState.selectedIds)} /> )} {tableProps.tableActions?.map((group, idx) => ( diff --git a/src/frontend/src/tables/bom/BomTable.tsx b/src/frontend/src/tables/bom/BomTable.tsx index f9478e5e23..4109b0f89a 100644 --- a/src/frontend/src/tables/bom/BomTable.tsx +++ b/src/frontend/src/tables/bom/BomTable.tsx @@ -1,5 +1,6 @@ import { t } from '@lingui/macro'; import { Group, Text } from '@mantine/core'; +import { showNotification } from '@mantine/notifications'; import { IconArrowRight, IconCircleCheck, @@ -8,6 +9,7 @@ import { import { ReactNode, useCallback, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { api } from '../../App'; import { AddItemButton } from '../../components/buttons/AddItemButton'; import { YesNoButton } from '../../components/buttons/YesNoButton'; import { Thumbnail } from '../../components/images/Thumbnail'; @@ -33,7 +35,7 @@ import { } from '../ColumnRenderers'; import { TableFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; -import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions'; +import { RowDeleteAction, RowEditAction } from '../RowActions'; import { TableHoverCard } from '../TableHoverCard'; // Calculate the total stock quantity available for a given BomItem @@ -146,6 +148,9 @@ export function BomTable({ // TODO: Custom renderer for this column // TODO: See bom.js for existing implementation }), + BooleanColumn({ + accessor: 'validated' + }), { accessor: 'price_range', title: t`Unit Price`, @@ -339,6 +344,29 @@ export function BomTable({ 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( (record: any) => { // 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[] = []; - - // TODO: Enable BomItem validation - actions.push({ - title: t`Validate BOM line`, - color: 'green', - hidden: record.validated || !user.hasChangeRole(UserRoles.part), - icon: - }); - - // TODO: Enable editing of substitutes - actions.push({ - title: t`Edit Substitutes`, - color: 'blue', - hidden: !user.hasChangeRole(UserRoles.part), - icon: - }); - - // Action on edit - actions.push( + return [ + { + title: t`Validate BOM Line`, + color: 'green', + hidden: record.validated || !user.hasChangeRole(UserRoles.part), + icon: , + onClick: () => validateBomItem(record) + }, RowEditAction({ hidden: !user.hasChangeRole(UserRoles.part), onClick: () => { setSelectedBomItem(record.pk); editBomItem.open(); } - }) - ); - - // Action on delete - actions.push( + }), + { + title: t`Edit Substitutes`, + color: 'blue', + hidden: !user.hasChangeRole(UserRoles.part), + icon: + }, RowDeleteAction({ hidden: !user.hasDeleteRole(UserRoles.part), onClick: () => { @@ -390,9 +408,7 @@ export function BomTable({ deleteBomItem.open(); } }) - ); - - return actions; + ]; }, [partId, user] ); @@ -427,7 +443,9 @@ export function BomTable({ tableFilters: tableFilters, modelType: ModelType.part, modelField: 'sub_part', - rowActions: rowActions + rowActions: rowActions, + enableSelection: true, + enableBulkDelete: true }} />