mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +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:
		| @@ -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', | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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/', | ||||||
|   | |||||||
| @@ -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) => ( | ||||||
|   | |||||||
| @@ -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 | ||||||
|         }} |         }} | ||||||
|       /> |       /> | ||||||
|     </> |     </> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user