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' }} /> );