diff --git a/src/frontend/src/components/render/Part.tsx b/src/frontend/src/components/render/Part.tsx
index 78cd5aae42..fc11a73c86 100644
--- a/src/frontend/src/components/render/Part.tsx
+++ b/src/frontend/src/components/render/Part.tsx
@@ -26,6 +26,9 @@ export function RenderPart(
if (instance.active == false) {
badgeColor = 'red';
badgeText = t`Inactive`;
+ } else if (instance.virtual) {
+ badgeColor = 'blue';
+ badgeText = t`Virtual`;
} else if (stock != null && stock <= 0) {
badgeColor = 'orange';
badgeText = t`No stock`;
diff --git a/src/frontend/src/tables/bom/BomTable.tsx b/src/frontend/src/tables/bom/BomTable.tsx
index 194d696e42..401b0c109b 100644
--- a/src/frontend/src/tables/bom/BomTable.tsx
+++ b/src/frontend/src/tables/bom/BomTable.tsx
@@ -285,10 +285,16 @@ export function BomTable({
render: (record: any) => {
const extra: ReactNode[] = [];
+ const part = record.sub_part_detail;
+
const available_stock: number = availableStockQuantity(record);
const on_order: number = record?.on_order ?? 0;
const building: number = record?.building ?? 0;
+ if (part?.virtual) {
+ return {t`Virtual part`};
+ }
+
const text =
available_stock <= 0 ? (
{t`No stock`}
@@ -352,10 +358,17 @@ export function BomTable({
title: t`Can Build`,
sortable: true,
render: (record: any) => {
+ // Virtual sub-part - the "can build" quantity does not make sense here
+ if (record.sub_part_detail?.virtual) {
+ return '-';
+ }
+
+ // No information available
if (record.can_build === null || record.can_build === undefined) {
return '-';
}
+ // NaN or infinite values
if (
!Number.isFinite(record.can_build) ||
Number.isNaN(record.can_build)
diff --git a/src/frontend/tests/pages/pui_part.spec.ts b/src/frontend/tests/pages/pui_part.spec.ts
index 29cc43aa48..ac9eb973c5 100644
--- a/src/frontend/tests/pages/pui_part.spec.ts
+++ b/src/frontend/tests/pages/pui_part.spec.ts
@@ -604,7 +604,7 @@ test('Parts - Revision', async ({ browser }) => {
.getByText('Green Round Table (revision B) | B', { exact: true })
.click();
await page
- .getByRole('option', { name: 'Thumbnail Green Round Table No stock' })
+ .getByRole('option', { name: 'Thumbnail Green Round Table Virtual' })
.click();
await page.waitForURL('**/web/part/101/**');