mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Build allocation table updates (#7106)
* Update build line allocation table - Allow display of "tracked" items in main allocation table * Add resolveItem function for finding nested items * Update BuildLineTable * Allow BuildLineList to be ordered by 'trackable' field * Bump API version
This commit is contained in:
parent
8f2ef39282
commit
3e52e5fd69
@ -1,11 +1,14 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# 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."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
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
|
v191 - 2024-04-22 : https://github.com/inventree/InvenTree/pull/7079
|
||||||
- Adds API endpoints for Contenttype model
|
- Adds API endpoints for Contenttype model
|
||||||
|
|
||||||
|
@ -349,6 +349,7 @@ class BuildLineList(BuildLineEndpoint, ListCreateAPI):
|
|||||||
'optional',
|
'optional',
|
||||||
'unit_quantity',
|
'unit_quantity',
|
||||||
'available_stock',
|
'available_stock',
|
||||||
|
'trackable',
|
||||||
]
|
]
|
||||||
|
|
||||||
ordering_field_aliases = {
|
ordering_field_aliases = {
|
||||||
@ -357,6 +358,7 @@ class BuildLineList(BuildLineEndpoint, ListCreateAPI):
|
|||||||
'unit_quantity': 'bom_item__quantity',
|
'unit_quantity': 'bom_item__quantity',
|
||||||
'consumable': 'bom_item__consumable',
|
'consumable': 'bom_item__consumable',
|
||||||
'optional': 'bom_item__optional',
|
'optional': 'bom_item__optional',
|
||||||
|
'trackable': 'bom_item__sub_part__trackable',
|
||||||
}
|
}
|
||||||
|
|
||||||
search_fields = [
|
search_fields = [
|
||||||
|
@ -2436,11 +2436,9 @@ function loadBuildLineTable(table, build_id, options={}) {
|
|||||||
params.build = build_id;
|
params.build = build_id;
|
||||||
|
|
||||||
if (output) {
|
if (output) {
|
||||||
|
params.tracked = true;
|
||||||
params.output = output;
|
params.output = output;
|
||||||
name += `-${output}`;
|
name += `-${output}`;
|
||||||
} else {
|
|
||||||
// Default to untracked parts for the build
|
|
||||||
params.tracked = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let filters = loadTableFilters('buildlines', params);
|
let filters = loadTableFilters('buildlines', params);
|
||||||
@ -2649,7 +2647,11 @@ function loadBuildLineTable(table, build_id, options={}) {
|
|||||||
|
|
||||||
if (row.part_detail.trackable && !options.output) {
|
if (row.part_detail.trackable && !options.output) {
|
||||||
// Tracked parts must be allocated to a specific build output
|
// Tracked parts must be allocated to a specific build output
|
||||||
return `<em>{% trans "Tracked item" %}</em>`;
|
return `
|
||||||
|
<div>
|
||||||
|
<em>{% trans "Tracked item" %}</em>
|
||||||
|
<span title='{% trans "Allocate tracked items against individual build outputs" %}' class='fas fa-info-circle icon-blue' />
|
||||||
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row.allocated < row.quantity) {
|
if (row.allocated < row.quantity) {
|
||||||
|
@ -19,3 +19,16 @@ export function isTrue(value: any): boolean {
|
|||||||
|
|
||||||
return ['true', 'yes', '1', 'on', 't', 'y'].includes(s);
|
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);
|
||||||
|
}
|
||||||
|
@ -203,8 +203,7 @@ export default function BuildDetail() {
|
|||||||
content: build?.pk ? (
|
content: build?.pk ? (
|
||||||
<BuildLineTable
|
<BuildLineTable
|
||||||
params={{
|
params={{
|
||||||
build: id,
|
build: id
|
||||||
tracked: false
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Anchor } from '@mantine/core';
|
import { Anchor } from '@mantine/core';
|
||||||
|
import { access } from 'fs';
|
||||||
|
|
||||||
import { YesNoButton } from '../components/buttons/YesNoButton';
|
import { YesNoButton } from '../components/buttons/YesNoButton';
|
||||||
import { Thumbnail } from '../components/images/Thumbnail';
|
import { Thumbnail } from '../components/images/Thumbnail';
|
||||||
@ -11,6 +12,7 @@ import { TableStatusRenderer } from '../components/render/StatusRenderer';
|
|||||||
import { RenderOwner } from '../components/render/User';
|
import { RenderOwner } from '../components/render/User';
|
||||||
import { formatCurrency, renderDate } from '../defaults/formatters';
|
import { formatCurrency, renderDate } from '../defaults/formatters';
|
||||||
import { ModelType } from '../enums/ModelType';
|
import { ModelType } from '../enums/ModelType';
|
||||||
|
import { resolveItem } from '../functions/conversion';
|
||||||
import { cancelEvent } from '../functions/events';
|
import { cancelEvent } from '../functions/events';
|
||||||
import { TableColumn } from './Column';
|
import { TableColumn } from './Column';
|
||||||
import { ProjectCodeHoverCard } from './TableHoverCard';
|
import { ProjectCodeHoverCard } from './TableHoverCard';
|
||||||
@ -29,19 +31,24 @@ export function BooleanColumn({
|
|||||||
accessor,
|
accessor,
|
||||||
title,
|
title,
|
||||||
sortable,
|
sortable,
|
||||||
switchable
|
switchable,
|
||||||
|
ordering
|
||||||
}: {
|
}: {
|
||||||
accessor: string;
|
accessor: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
ordering?: string;
|
||||||
sortable?: boolean;
|
sortable?: boolean;
|
||||||
switchable?: boolean;
|
switchable?: boolean;
|
||||||
}): TableColumn {
|
}): TableColumn {
|
||||||
return {
|
return {
|
||||||
accessor: accessor,
|
accessor: accessor,
|
||||||
title: title,
|
title: title,
|
||||||
|
ordering: ordering,
|
||||||
sortable: sortable ?? true,
|
sortable: sortable ?? true,
|
||||||
switchable: switchable ?? true,
|
switchable: switchable ?? true,
|
||||||
render: (record: any) => <YesNoButton value={record[accessor]} />
|
render: (record: any) => (
|
||||||
|
<YesNoButton value={resolveItem(record, accessor)} />
|
||||||
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +78,7 @@ export function LinkColumn({
|
|||||||
accessor: accessor,
|
accessor: accessor,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
render: (record: any) => {
|
render: (record: any) => {
|
||||||
let url = record[accessor];
|
let url = resolveItem(record, accessor);
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return '-';
|
return '-';
|
||||||
|
@ -28,6 +28,7 @@ import { ActionButton } from '../components/buttons/ActionButton';
|
|||||||
import { ButtonMenu } from '../components/buttons/ButtonMenu';
|
import { ButtonMenu } from '../components/buttons/ButtonMenu';
|
||||||
import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
|
import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
|
||||||
import { ModelType } from '../enums/ModelType';
|
import { ModelType } from '../enums/ModelType';
|
||||||
|
import { resolveItem } from '../functions/conversion';
|
||||||
import { extractAvailableFields, mapFields } from '../functions/forms';
|
import { extractAvailableFields, mapFields } from '../functions/forms';
|
||||||
import { getDetailUrl } from '../functions/urls';
|
import { getDetailUrl } from '../functions/urls';
|
||||||
import { TableState } from '../hooks/UseTable';
|
import { TableState } from '../hooks/UseTable';
|
||||||
@ -519,7 +520,8 @@ export function InvenTreeTable<T = any>({
|
|||||||
// If a custom row click handler is provided, use that
|
// If a custom row click handler is provided, use that
|
||||||
props.onRowClick(record, index, event);
|
props.onRowClick(record, index, event);
|
||||||
} else if (tableProps.modelType) {
|
} else if (tableProps.modelType) {
|
||||||
const pk = record?.[tableProps.modelField ?? 'pk'];
|
const accessor = tableProps.modelField ?? 'pk';
|
||||||
|
const pk = resolveItem(record, accessor);
|
||||||
|
|
||||||
if (pk) {
|
if (pk) {
|
||||||
// If a model type is provided, navigate to the detail view for that model
|
// If a model type is provided, navigate to the detail view for that model
|
||||||
|
@ -6,13 +6,11 @@ import {
|
|||||||
IconTool
|
IconTool
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { PartHoverCard } from '../../components/images/Thumbnail';
|
import { PartHoverCard } from '../../components/images/Thumbnail';
|
||||||
import { ProgressBar } from '../../components/items/ProgressBar';
|
import { ProgressBar } from '../../components/items/ProgressBar';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { getDetailUrl } from '../../functions/urls';
|
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
@ -25,7 +23,6 @@ import { TableHoverCard } from '../TableHoverCard';
|
|||||||
export default function BuildLineTable({ params = {} }: { params?: any }) {
|
export default function BuildLineTable({ params = {} }: { params?: any }) {
|
||||||
const table = useTable('buildline');
|
const table = useTable('buildline');
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const tableFilters: TableFilter[] = useMemo(() => {
|
const tableFilters: TableFilter[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
@ -47,6 +44,11 @@ export default function BuildLineTable({ params = {} }: { params?: any }) {
|
|||||||
name: 'optional',
|
name: 'optional',
|
||||||
label: t`Optional`,
|
label: t`Optional`,
|
||||||
description: t`Show optional lines`
|
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 [
|
return [
|
||||||
{
|
{
|
||||||
accessor: 'bom_item',
|
accessor: 'bom_item',
|
||||||
|
ordering: 'part',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: false,
|
switchable: false,
|
||||||
render: (record: any) => <PartHoverCard part={record.part_detail} />
|
render: (record: any) => <PartHoverCard part={record.part_detail} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'bom_item_detail.reference'
|
accessor: 'bom_item_detail.reference',
|
||||||
|
ordering: 'reference',
|
||||||
|
sortable: true,
|
||||||
|
title: t`Reference`
|
||||||
},
|
},
|
||||||
BooleanColumn({
|
BooleanColumn({
|
||||||
accessor: 'bom_item_detail.consumable'
|
accessor: 'bom_item_detail.consumable',
|
||||||
|
ordering: 'consumable'
|
||||||
}),
|
}),
|
||||||
BooleanColumn({
|
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',
|
accessor: 'bom_item_detail.quantity',
|
||||||
@ -198,6 +210,11 @@ export default function BuildLineTable({ params = {} }: { params?: any }) {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tracked items must be allocated to a particular output
|
||||||
|
if (record?.part_detail?.trackable) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
icon: <IconArrowRight />,
|
icon: <IconArrowRight />,
|
||||||
@ -234,11 +251,8 @@ export default function BuildLineTable({ params = {} }: { params?: any }) {
|
|||||||
},
|
},
|
||||||
tableFilters: tableFilters,
|
tableFilters: tableFilters,
|
||||||
rowActions: rowActions,
|
rowActions: rowActions,
|
||||||
onRowClick: (row: any) => {
|
modelType: ModelType.part,
|
||||||
if (row?.part_detail?.pk) {
|
modelField: 'part_detail.pk'
|
||||||
navigate(getDetailUrl(ModelType.part, row.part_detail.pk));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user