diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx
index 64cb40df98..821766af5f 100644
--- a/src/frontend/src/pages/part/PartDetail.tsx
+++ b/src/frontend/src/pages/part/PartDetail.tsx
@@ -675,7 +675,7 @@ export default function PartDetail() {
icon: ,
hidden: !part.trackable,
content: part?.pk ? (
-
+
) : (
)
diff --git a/src/frontend/src/tables/build/BuildLineTable.tsx b/src/frontend/src/tables/build/BuildLineTable.tsx
index 1940a95de2..f51e5d711a 100644
--- a/src/frontend/src/tables/build/BuildLineTable.tsx
+++ b/src/frontend/src/tables/build/BuildLineTable.tsx
@@ -24,9 +24,11 @@ import { TableHoverCard } from '../TableHoverCard';
export default function BuildLineTable({
buildId,
+ outputId,
params = {}
}: {
buildId: number;
+ outputId?: number;
params?: any;
}) {
const table = useTable('buildline');
@@ -237,35 +239,42 @@ export default function BuildLineTable({
const rowActions = useCallback(
(record: any) => {
- let part = record.part_detail;
+ let part = record.part_detail ?? {};
// Consumable items have no appropriate actions
if (record?.bom_item_detail?.consumable) {
return [];
}
- // Tracked items must be allocated to a particular output
- if (record?.part_detail?.trackable) {
- return [];
- }
+ const hasOutput = !!outputId;
+
+ // Can allocate
+ let canAllocate =
+ user.hasChangeRole(UserRoles.build) &&
+ record.allocated < record.quantity &&
+ record.trackable == hasOutput;
+
+ let canOrder =
+ user.hasAddRole(UserRoles.purchase_order) && part.purchaseable;
+ let canBuild = user.hasAddRole(UserRoles.build) && part.assembly;
return [
{
icon: ,
title: t`Allocate Stock`,
- hidden: record.allocated >= record.quantity,
+ hidden: !canAllocate,
color: 'green'
},
{
icon: ,
title: t`Order Stock`,
- hidden: !part?.purchaseable,
+ hidden: !canOrder,
color: 'blue'
},
{
icon: ,
title: t`Build Stock`,
- hidden: !part?.assembly || !user.hasAddRole(UserRoles.build),
+ hidden: !canBuild,
color: 'blue',
onClick: () => {
setInitialData({
@@ -278,7 +287,7 @@ export default function BuildLineTable({
}
];
},
- [user]
+ [user, outputId]
);
return (
diff --git a/src/frontend/src/tables/part/PartTestTemplateTable.tsx b/src/frontend/src/tables/part/PartTestTemplateTable.tsx
index 6dd0af662d..1545663a2e 100644
--- a/src/frontend/src/tables/part/PartTestTemplateTable.tsx
+++ b/src/frontend/src/tables/part/PartTestTemplateTable.tsx
@@ -1,6 +1,7 @@
import { Trans, t } from '@lingui/macro';
-import { Alert, Badge, Text } from '@mantine/core';
-import { useCallback, useMemo, useState } from 'react';
+import { Alert, Badge, Stack, Text } from '@mantine/core';
+import { IconArrowRight, IconLock } from '@tabler/icons-react';
+import { ReactNode, useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { AddItemButton } from '../../components/buttons/AddItemButton';
@@ -22,8 +23,15 @@ import { BooleanColumn, DescriptionColumn } from '../ColumnRenderers';
import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable';
import { RowDeleteAction, RowEditAction } from '../RowActions';
+import { TableHoverCard } from '../TableHoverCard';
-export default function PartTestTemplateTable({ partId }: { partId: number }) {
+export default function PartTestTemplateTable({
+ partId,
+ partLocked
+}: {
+ partId: number;
+ partLocked?: boolean;
+}) {
const table = useTable('part-test-template');
const user = useUserState();
const navigate = useNavigate();
@@ -35,13 +43,27 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) {
switchable: false,
sortable: true,
render: (record: any) => {
+ let extra: ReactNode[] = [];
+
+ if (record.part != partId) {
+ extra.push(
+ {t`Test is defined for a parent template part`}
+ );
+ }
+
return (
-
- {record.test_name}
-
+
+ {record.test_name}
+
+ }
+ title={t`Template Details`}
+ extra={extra}
+ />
);
}
},
@@ -175,20 +197,28 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) {
const can_delete = user.hasDeleteRole(UserRoles.part);
if (record.part != partId) {
- // No actions, as this test is defined for a parent part
- return [];
+ // This test is defined for a parent part
+ return [
+ {
+ icon: ,
+ title: t`View Parent Part`,
+ onClick: () => {
+ navigate(getDetailUrl(ModelType.part, record.part));
+ }
+ }
+ ];
}
return [
RowEditAction({
- hidden: !can_edit,
+ hidden: partLocked || !can_edit,
onClick: () => {
setSelectedTest(record.pk);
editTestTemplate.open();
}
}),
RowDeleteAction({
- hidden: !can_delete,
+ hidden: partLocked || !can_delete,
onClick: () => {
setSelectedTest(record.pk);
deleteTestTemplate.open();
@@ -196,7 +226,7 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) {
})
];
},
- [user, partId]
+ [user, partId, partLocked]
);
const tableActions = useMemo(() => {
@@ -206,36 +236,49 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) {
newTestTemplate.open()}
- hidden={!can_add}
+ hidden={partLocked || !can_add}
/>
];
- }, [user]);
+ }, [user, partLocked]);
return (
<>
{newTestTemplate.modal}
{editTestTemplate.modal}
{deleteTestTemplate.modal}
- {
- if (row.part && row.part != partId) {
- // This test is defined for a different part
- navigate(getDetailUrl(ModelType.part, row.part));
+
+ {partLocked && (
+ }
+ p="xs"
+ >
+ {t`Part templates cannot be edited, as the part is locked`}
+
+ )}
+ {
+ if (row.part && row.part != partId) {
+ // This test is defined for a different part
+ navigate(getDetailUrl(ModelType.part, row.part));
+ }
}
- }
- }}
- />
+ }}
+ />
+
>
);
}