diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py
index 0b50283160..5f43e0305c 100644
--- a/src/backend/InvenTree/InvenTree/api_version.py
+++ b/src/backend/InvenTree/InvenTree/api_version.py
@@ -1,11 +1,14 @@
"""InvenTree API version information."""
# InvenTree API version
-INVENTREE_API_VERSION = 191
+INVENTREE_API_VERSION = 192
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
+v192 - 2024-04-23 : https://github.com/inventree/InvenTree/pull/7106
+ - Adds 'trackable' ordering option to BuildLineLabel API endpoint
+
v191 - 2024-04-22 : https://github.com/inventree/InvenTree/pull/7079
- Adds API endpoints for Contenttype model
diff --git a/src/backend/InvenTree/build/api.py b/src/backend/InvenTree/build/api.py
index 97ead63494..e6e6267fe2 100644
--- a/src/backend/InvenTree/build/api.py
+++ b/src/backend/InvenTree/build/api.py
@@ -349,6 +349,7 @@ class BuildLineList(BuildLineEndpoint, ListCreateAPI):
'optional',
'unit_quantity',
'available_stock',
+ 'trackable',
]
ordering_field_aliases = {
@@ -357,6 +358,7 @@ class BuildLineList(BuildLineEndpoint, ListCreateAPI):
'unit_quantity': 'bom_item__quantity',
'consumable': 'bom_item__consumable',
'optional': 'bom_item__optional',
+ 'trackable': 'bom_item__sub_part__trackable',
}
search_fields = [
diff --git a/src/backend/InvenTree/templates/js/translated/build.js b/src/backend/InvenTree/templates/js/translated/build.js
index a8026414b7..899866ae35 100644
--- a/src/backend/InvenTree/templates/js/translated/build.js
+++ b/src/backend/InvenTree/templates/js/translated/build.js
@@ -2436,11 +2436,9 @@ function loadBuildLineTable(table, build_id, options={}) {
params.build = build_id;
if (output) {
+ params.tracked = true;
params.output = output;
name += `-${output}`;
- } else {
- // Default to untracked parts for the build
- params.tracked = false;
}
let filters = loadTableFilters('buildlines', params);
@@ -2649,7 +2647,11 @@ function loadBuildLineTable(table, build_id, options={}) {
if (row.part_detail.trackable && !options.output) {
// Tracked parts must be allocated to a specific build output
- return `{% trans "Tracked item" %}`;
+ return `
+
+ {% trans "Tracked item" %}
+
+
`;
}
if (row.allocated < row.quantity) {
diff --git a/src/frontend/src/functions/conversion.tsx b/src/frontend/src/functions/conversion.tsx
index 1ad406570e..afc4e536d2 100644
--- a/src/frontend/src/functions/conversion.tsx
+++ b/src/frontend/src/functions/conversion.tsx
@@ -19,3 +19,16 @@ export function isTrue(value: any): boolean {
return ['true', 'yes', '1', 'on', 't', 'y'].includes(s);
}
+
+/*
+ * Resolve a nested item in an object.
+ * Returns the resolved item, if it exists.
+ *
+ * e.g. resolveItem(data, "sub.key.accessor")
+ *
+ * Allows for retrieval of nested items in an object.
+ */
+export function resolveItem(obj: any, path: string): any {
+ let properties = path.split('.');
+ return properties.reduce((prev, curr) => prev?.[curr], obj);
+}
diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx
index 8ec7b03444..1f6bb03f0d 100644
--- a/src/frontend/src/pages/build/BuildDetail.tsx
+++ b/src/frontend/src/pages/build/BuildDetail.tsx
@@ -203,8 +203,7 @@ export default function BuildDetail() {
content: build?.pk ? (
) : (
diff --git a/src/frontend/src/tables/ColumnRenderers.tsx b/src/frontend/src/tables/ColumnRenderers.tsx
index f1d39858cf..654ddd9409 100644
--- a/src/frontend/src/tables/ColumnRenderers.tsx
+++ b/src/frontend/src/tables/ColumnRenderers.tsx
@@ -3,6 +3,7 @@
*/
import { t } from '@lingui/macro';
import { Anchor } from '@mantine/core';
+import { access } from 'fs';
import { YesNoButton } from '../components/buttons/YesNoButton';
import { Thumbnail } from '../components/images/Thumbnail';
@@ -11,6 +12,7 @@ import { TableStatusRenderer } from '../components/render/StatusRenderer';
import { RenderOwner } from '../components/render/User';
import { formatCurrency, renderDate } from '../defaults/formatters';
import { ModelType } from '../enums/ModelType';
+import { resolveItem } from '../functions/conversion';
import { cancelEvent } from '../functions/events';
import { TableColumn } from './Column';
import { ProjectCodeHoverCard } from './TableHoverCard';
@@ -29,19 +31,24 @@ export function BooleanColumn({
accessor,
title,
sortable,
- switchable
+ switchable,
+ ordering
}: {
accessor: string;
title?: string;
+ ordering?: string;
sortable?: boolean;
switchable?: boolean;
}): TableColumn {
return {
accessor: accessor,
title: title,
+ ordering: ordering,
sortable: sortable ?? true,
switchable: switchable ?? true,
- render: (record: any) =>
+ render: (record: any) => (
+
+ )
};
}
@@ -71,7 +78,7 @@ export function LinkColumn({
accessor: accessor,
sortable: false,
render: (record: any) => {
- let url = record[accessor];
+ let url = resolveItem(record, accessor);
if (!url) {
return '-';
diff --git a/src/frontend/src/tables/InvenTreeTable.tsx b/src/frontend/src/tables/InvenTreeTable.tsx
index 93c17f67a8..fa1391bbb4 100644
--- a/src/frontend/src/tables/InvenTreeTable.tsx
+++ b/src/frontend/src/tables/InvenTreeTable.tsx
@@ -28,6 +28,7 @@ import { ActionButton } from '../components/buttons/ActionButton';
import { ButtonMenu } from '../components/buttons/ButtonMenu';
import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
import { ModelType } from '../enums/ModelType';
+import { resolveItem } from '../functions/conversion';
import { extractAvailableFields, mapFields } from '../functions/forms';
import { getDetailUrl } from '../functions/urls';
import { TableState } from '../hooks/UseTable';
@@ -519,7 +520,8 @@ export function InvenTreeTable({
// If a custom row click handler is provided, use that
props.onRowClick(record, index, event);
} else if (tableProps.modelType) {
- const pk = record?.[tableProps.modelField ?? 'pk'];
+ const accessor = tableProps.modelField ?? 'pk';
+ const pk = resolveItem(record, accessor);
if (pk) {
// If a model type is provided, navigate to the detail view for that model
diff --git a/src/frontend/src/tables/build/BuildLineTable.tsx b/src/frontend/src/tables/build/BuildLineTable.tsx
index edd93949be..7ed7d4367c 100644
--- a/src/frontend/src/tables/build/BuildLineTable.tsx
+++ b/src/frontend/src/tables/build/BuildLineTable.tsx
@@ -6,13 +6,11 @@ import {
IconTool
} from '@tabler/icons-react';
import { useCallback, useMemo } from 'react';
-import { useNavigate } from 'react-router-dom';
import { PartHoverCard } from '../../components/images/Thumbnail';
import { ProgressBar } from '../../components/items/ProgressBar';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
-import { getDetailUrl } from '../../functions/urls';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
@@ -25,7 +23,6 @@ import { TableHoverCard } from '../TableHoverCard';
export default function BuildLineTable({ params = {} }: { params?: any }) {
const table = useTable('buildline');
const user = useUserState();
- const navigate = useNavigate();
const tableFilters: TableFilter[] = useMemo(() => {
return [
@@ -47,6 +44,11 @@ export default function BuildLineTable({ params = {} }: { params?: any }) {
name: 'optional',
label: t`Optional`,
description: t`Show optional lines`
+ },
+ {
+ name: 'tracked',
+ label: t`Tracked`,
+ description: t`Show tracked lines`
}
];
}, []);
@@ -122,18 +124,28 @@ export default function BuildLineTable({ params = {} }: { params?: any }) {
return [
{
accessor: 'bom_item',
+ ordering: 'part',
sortable: true,
switchable: false,
render: (record: any) =>
},
{
- accessor: 'bom_item_detail.reference'
+ accessor: 'bom_item_detail.reference',
+ ordering: 'reference',
+ sortable: true,
+ title: t`Reference`
},
BooleanColumn({
- accessor: 'bom_item_detail.consumable'
+ accessor: 'bom_item_detail.consumable',
+ ordering: 'consumable'
}),
BooleanColumn({
- accessor: 'bom_item_detail.optional'
+ accessor: 'bom_item_detail.optional',
+ ordering: 'optional'
+ }),
+ BooleanColumn({
+ accessor: 'part_detail.trackable',
+ ordering: 'trackable'
}),
{
accessor: 'bom_item_detail.quantity',
@@ -198,6 +210,11 @@ export default function BuildLineTable({ params = {} }: { params?: any }) {
return [];
}
+ // Tracked items must be allocated to a particular output
+ if (record?.part_detail?.trackable) {
+ return [];
+ }
+
return [
{
icon: ,
@@ -234,11 +251,8 @@ export default function BuildLineTable({ params = {} }: { params?: any }) {
},
tableFilters: tableFilters,
rowActions: rowActions,
- onRowClick: (row: any) => {
- if (row?.part_detail?.pk) {
- navigate(getDetailUrl(ModelType.part, row.part_detail.pk));
- }
- }
+ modelType: ModelType.part,
+ modelField: 'part_detail.pk'
}}
/>
);