2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-04-28 13:54:25 +00:00

[UI] Invalid BOM warning (#11817)

* [UI] Invalid BOM warning

* Add similar warning to BOM table

* Adjust bom subassembly table
This commit is contained in:
Oliver
2026-04-27 18:17:49 +10:00
committed by GitHub
parent 27ebfeba1c
commit 9958f4a247
3 changed files with 75 additions and 24 deletions
@@ -5,6 +5,7 @@ import {
IconCircleCheck, IconCircleCheck,
IconClipboardCheck, IconClipboardCheck,
IconClipboardList, IconClipboardList,
IconExclamationCircle,
IconInfoCircle, IconInfoCircle,
IconList, IconList,
IconListCheck, IconListCheck,
@@ -88,6 +89,13 @@ function BuildLinesPanel({
isLoading: boolean; isLoading: boolean;
hasItems: boolean; hasItems: boolean;
}>) { }>) {
const bomInformation = useInstance({
endpoint: ApiEndpoints.bom_validate,
pk: build?.part,
hasPrimaryKey: true,
refetchOnMount: true
});
const buildLocation = useInstance({ const buildLocation = useInstance({
endpoint: ApiEndpoints.stock_location_list, endpoint: ApiEndpoints.stock_location_list,
pk: build?.take_from, pk: build?.take_from,
@@ -105,6 +113,16 @@ function BuildLinesPanel({
return ( return (
<Stack gap='xs'> <Stack gap='xs'>
{bomInformation?.isLoaded &&
bomInformation?.instance?.bom_validated == false && (
<Alert
color='orange'
icon={<IconExclamationCircle />}
title={t`BOM Not Validated`}
>
<Text>{t`The Bill of Materials for this assembly has not been validated.`}</Text>
</Alert>
)}
{buildLocation.instance.pk && ( {buildLocation.instance.pk && (
<Alert color='blue' icon={<IconSitemap />} title={t`Source Location`}> <Alert color='blue' icon={<IconSitemap />} title={t`Source Location`}>
<RenderStockLocation instance={buildLocation.instance} /> <RenderStockLocation instance={buildLocation.instance} />
+52 -22
View File
@@ -89,7 +89,7 @@ import {
useDeleteApiFormModal, useDeleteApiFormModal,
useEditApiFormModal useEditApiFormModal
} from '../../hooks/UseForm'; } from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance'; import { type UseInstanceResult, useInstance } from '../../hooks/UseInstance';
import { useStockAdjustActions } from '../../hooks/UseStockAdjustActions'; import { useStockAdjustActions } from '../../hooks/UseStockAdjustActions';
import { import {
useGlobalSettingsState, useGlobalSettingsState,
@@ -158,18 +158,12 @@ function RevisionSelector({
* A hover-over component which displays information about the BOM validation for a given part * A hover-over component which displays information about the BOM validation for a given part
*/ */
function BomValidationInformation({ function BomValidationInformation({
bomInformation,
partId partId
}: { }: {
bomInformation: UseInstanceResult;
partId: number; partId: number;
}) { }) {
const { instance: bomInformation, instanceQuery: bomInformationQuery } =
useInstance({
endpoint: ApiEndpoints.bom_validate,
pk: partId,
hasPrimaryKey: true,
refetchOnMount: true
});
const [taskId, setTaskId] = useState<string>(''); const [taskId, setTaskId] = useState<string>('');
useBackgroundTask({ useBackgroundTask({
@@ -177,7 +171,7 @@ function BomValidationInformation({
message: t`Validating BOM`, message: t`Validating BOM`,
successMessage: t`BOM validated`, successMessage: t`BOM validated`,
onComplete: () => { onComplete: () => {
bomInformationQuery.refetch(); bomInformation.instanceQuery.refetch();
} }
}); });
@@ -203,12 +197,12 @@ function BomValidationInformation({
if (response.task_id) { if (response.task_id) {
setTaskId(response.task_id); setTaskId(response.task_id);
} else { } else {
bomInformationQuery.refetch(); bomInformation.instanceQuery.refetch();
} }
} }
}); });
if (bomInformationQuery.isFetching) { if (bomInformation.instanceQuery.isFetching) {
return <Loader size='sm' />; return <Loader size='sm' />;
} }
@@ -217,12 +211,12 @@ function BomValidationInformation({
let title = ''; let title = '';
let description = ''; let description = '';
if (bomInformation?.bom_validated) { if (bomInformation.instance?.bom_validated) {
color = 'green'; color = 'green';
icon = <IconListCheck />; icon = <IconListCheck />;
title = t`BOM Validated`; title = t`BOM Validated`;
description = t`The Bill of Materials for this part has been validated`; description = t`The Bill of Materials for this part has been validated`;
} else if (bomInformation?.bom_checked_date) { } else if (bomInformation.instance?.bom_checked_date) {
color = 'yellow'; color = 'yellow';
icon = <IconExclamationCircle />; icon = <IconExclamationCircle />;
title = t`BOM Not Validated`; title = t`BOM Not Validated`;
@@ -238,7 +232,7 @@ function BomValidationInformation({
<> <>
{validateBom.modal} {validateBom.modal}
<Group gap='xs' justify='flex-end'> <Group gap='xs' justify='flex-end'>
{!bomInformation.bom_validated && ( {!bomInformation.instance?.bom_validated && (
<ActionButton <ActionButton
icon={<IconCircleCheck />} icon={<IconCircleCheck />}
color='green' color='green'
@@ -260,16 +254,17 @@ function BomValidationInformation({
<Alert color={color} icon={icon} title={title}> <Alert color={color} icon={icon} title={title}>
<Stack gap='xs'> <Stack gap='xs'>
<Text size='sm'>{description}</Text> <Text size='sm'>{description}</Text>
{bomInformation?.bom_checked_date && ( {bomInformation.instance?.bom_checked_date && (
<Text size='sm'> <Text size='sm'>
{t`Validated On`}: {bomInformation.bom_checked_date} {t`Validated On`}:{' '}
{bomInformation.instance.bom_checked_date}
</Text> </Text>
)} )}
{bomInformation?.bom_checked_by_detail && ( {bomInformation.instance?.bom_checked_by_detail && (
<Group gap='xs'> <Group gap='xs'>
<Text size='sm'>{t`Validated By`}: </Text> <Text size='sm'>{t`Validated By`}: </Text>
<RenderUser <RenderUser
instance={bomInformation.bom_checked_by_detail} instance={bomInformation.instance.bom_checked_by_detail}
/> />
</Group> </Group>
)} )}
@@ -297,6 +292,13 @@ export default function PartDetail() {
const globalSettings = useGlobalSettingsState(); const globalSettings = useGlobalSettingsState();
const userSettings = useUserSettingsState(); const userSettings = useUserSettingsState();
const bomInformation = useInstance({
endpoint: ApiEndpoints.bom_validate,
pk: id,
hasPrimaryKey: true,
refetchOnMount: true
});
const { instance: serials } = useInstance({ const { instance: serials } = useInstance({
endpoint: ApiEndpoints.part_serial_numbers, endpoint: ApiEndpoints.part_serial_numbers,
pk: id, pk: id,
@@ -802,11 +804,31 @@ export default function PartDetail() {
{ {
name: 'bom', name: 'bom',
label: t`Bill of Materials`, label: t`Bill of Materials`,
controls: <BomValidationInformation partId={part.pk ?? -1} />, controls: (
<BomValidationInformation
bomInformation={bomInformation}
partId={part.pk ?? -1}
/>
),
icon: <IconListTree />, icon: <IconListTree />,
hidden: !part.assembly, hidden: !part.assembly,
content: part?.pk ? ( content: part?.pk ? (
<BomTable partId={part.pk ?? -1} partLocked={part?.locked == true} /> <Stack gap='xs'>
{bomInformation.isLoaded &&
bomInformation.instance?.bom_validated === false && (
<Alert
color='yellow'
icon={<IconExclamationCircle />}
title={t`BOM Not Validated`}
>
<Text>{t`The Bill of Materials for this assembly has not been validated.`}</Text>
</Alert>
)}
<BomTable
partId={part.pk ?? -1}
partLocked={part?.locked == true}
/>
</Stack>
) : ( ) : (
<Skeleton /> <Skeleton />
) )
@@ -950,7 +972,15 @@ export default function PartDetail() {
has_note: !!part?.notes has_note: !!part?.notes
}) })
]; ];
}, [id, part, user, globalSettings, userSettings, detailsPanel]); }, [
id,
part,
user,
globalSettings,
userSettings,
detailsPanel,
bomInformation
]);
const breadcrumbs = useMemo(() => { const breadcrumbs = useMemo(() => {
return [ return [
@@ -77,9 +77,12 @@ export default function BomSubassemblyTable({
IPNColumn({ IPNColumn({
accessor: 'sub_part_detail.IPN' accessor: 'sub_part_detail.IPN'
}), }),
ReferenceColumn({}), ReferenceColumn({
width: 400
}),
{ {
accessor: 'quantity' accessor: 'quantity',
width: 250
} }
]; ];
}, [table.isRowExpanded, userSettings]); }, [table.isRowExpanded, userSettings]);