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:
@@ -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} />
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|||||||
Reference in New Issue
Block a user