2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-01-09 04:38:00 +00:00

Location parameters (#11084)

* Add parameter support for StockLocation model

* Serialize parameters for stock location

* Frontend support

* Bump API version

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
Oliver
2026-01-07 12:57:20 +11:00
committed by GitHub
parent 20c381f862
commit b478254e98
7 changed files with 110 additions and 8 deletions

View File

@@ -1,11 +1,14 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 436 INVENTREE_API_VERSION = 437
"""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 = """
v437 -> 2026-01-07 : https://github.com/inventree/InvenTree/pull/11084
- Add generic parameter support for the StockLocation model
v436 -> 2026-01-06 : https://github.com/inventree/InvenTree/pull/11035 v436 -> 2026-01-06 : https://github.com/inventree/InvenTree/pull/11035
- Removes model-specific metadata endpoints and replaces them with redirects - Removes model-specific metadata endpoints and replaces them with redirects
- Adds new generic /api/metadata/<model_name>/ endpoint to retrieve metadata for any model - Adds new generic /api/metadata/<model_name>/ endpoint to retrieve metadata for any model

View File

@@ -120,6 +120,7 @@ class StockLocationReportContext(report.mixins.BaseReportContext):
class StockLocation( class StockLocation(
InvenTree.models.PluginValidationMixin, InvenTree.models.PluginValidationMixin,
InvenTree.models.InvenTreeParameterMixin,
InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.InvenTreeBarcodeMixin,
report.mixins.InvenTreeReportMixin, report.mixins.InvenTreeReportMixin,
InvenTree.models.PathStringMixin, InvenTree.models.PathStringMixin,

View File

@@ -1159,8 +1159,10 @@ class LocationSerializer(
'structural', 'structural',
'external', 'external',
'location_type', 'location_type',
# Optional fields
'location_type_detail', 'location_type_detail',
'tags', 'tags',
'parameters',
] ]
read_only_fields = ['barcode_hash', 'icon', 'level', 'pathstring'] read_only_fields = ['barcode_hash', 'icon', 'level', 'pathstring']
@@ -1205,6 +1207,8 @@ class LocationSerializer(
filter_name='path_detail', filter_name='path_detail',
) )
parameters = common.filters.enable_parameters_filter()
# explicitly set this field, so it gets included for AutoSchema # explicitly set this field, so it gets included for AutoSchema
icon = serializers.CharField(read_only=True) icon = serializers.CharField(read_only=True)

View File

@@ -8,16 +8,19 @@ import type { PanelType } from './Panel';
export default function ParametersPanel({ export default function ParametersPanel({
model_type, model_type,
model_id, model_id,
hidden,
allowEdit = true allowEdit = true
}: { }: {
model_type: ModelType; model_type: ModelType;
model_id: number | undefined; model_id: number | undefined;
hidden?: boolean;
allowEdit?: boolean; allowEdit?: boolean;
}): PanelType { }): PanelType {
return { return {
name: 'parameters', name: 'parameters',
label: t`Parameters`, label: t`Parameters`,
icon: <IconListDetails />, icon: <IconListDetails />,
hidden: hidden ?? false,
content: content:
model_type && model_id ? ( model_type && model_id ? (
<ParameterTable <ParameterTable

View File

@@ -6,7 +6,13 @@ import { getDetailUrl } from '@lib/functions/Navigation';
import type { StockOperationProps } from '@lib/types/Forms'; import type { StockOperationProps } from '@lib/types/Forms';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { Group, Skeleton, Stack, Text } from '@mantine/core'; import { Group, Skeleton, Stack, Text } from '@mantine/core';
import { IconInfoCircle, IconPackages, IconSitemap } from '@tabler/icons-react'; import {
IconInfoCircle,
IconListDetails,
IconPackages,
IconSitemap,
IconTable
} from '@tabler/icons-react';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { api } from '../../App'; import { api } from '../../App';
@@ -30,6 +36,8 @@ import NavigationTree from '../../components/nav/NavigationTree';
import { PageDetail } from '../../components/nav/PageDetail'; import { PageDetail } from '../../components/nav/PageDetail';
import type { PanelType } from '../../components/panels/Panel'; import type { PanelType } from '../../components/panels/Panel';
import { PanelGroup } from '../../components/panels/PanelGroup'; import { PanelGroup } from '../../components/panels/PanelGroup';
import ParametersPanel from '../../components/panels/ParametersPanel';
import SegmentedControlPanel from '../../components/panels/SegmentedControlPanel';
import LocateItemButton from '../../components/plugins/LocateItemButton'; import LocateItemButton from '../../components/plugins/LocateItemButton';
import { stockLocationFields } from '../../forms/StockForms'; import { stockLocationFields } from '../../forms/StockForms';
import { InvenTreeIcon } from '../../functions/icons'; import { InvenTreeIcon } from '../../functions/icons';
@@ -42,6 +50,7 @@ import { useStockAdjustActions } from '../../hooks/UseStockAdjustActions';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import { PartListTable } from '../../tables/part/PartTable'; import { PartListTable } from '../../tables/part/PartTable';
import { StockItemTable } from '../../tables/stock/StockItemTable'; import { StockItemTable } from '../../tables/stock/StockItemTable';
import StockLocationParametricTable from '../../tables/stock/StockLocationParametricTable';
import { StockLocationTable } from '../../tables/stock/StockLocationTable'; import { StockLocationTable } from '../../tables/stock/StockLocationTable';
export default function Stock() { export default function Stock() {
@@ -161,6 +170,8 @@ export default function Stock() {
); );
}, [location, instanceQuery]); }, [location, instanceQuery]);
const [sublocationView, setSublocationView] = useState<string>('table');
const locationPanels: PanelType[] = useMemo(() => { const locationPanels: PanelType[] = useMemo(() => {
return [ return [
{ {
@@ -169,12 +180,32 @@ export default function Stock() {
icon: <IconInfoCircle />, icon: <IconInfoCircle />,
content: detailsPanel content: detailsPanel
}, },
{ SegmentedControlPanel({
name: 'sublocations', name: 'sublocations',
label: id ? t`Sublocations` : t`Stock Locations`, label: id ? t`Sublocations` : t`Stock Locations`,
icon: <IconSitemap />, icon: <IconSitemap />,
content: <StockLocationTable parentId={id} /> hidden: !user.hasViewPermission(ModelType.stocklocation),
}, selection: sublocationView,
onChange: setSublocationView,
options: [
{
value: 'table',
label: t`Table View`,
icon: <IconTable />,
content: <StockLocationTable parentId={id} />
},
{
value: 'parametric',
label: t`Parametric View`,
icon: <IconListDetails />,
content: (
<StockLocationParametricTable
queryParams={id ? { parent: id } : {}}
/>
)
}
]
}),
{ {
name: 'stock-items', name: 'stock-items',
label: t`Stock Items`, label: t`Stock Items`,
@@ -203,9 +234,14 @@ export default function Stock() {
}} }}
/> />
) )
} },
ParametersPanel({
model_type: ModelType.stocklocation,
model_id: location.pk,
hidden: !location.pk
})
]; ];
}, [location, id]); }, [sublocationView, location, id]);
const editLocation = useEditApiFormModal({ const editLocation = useEditApiFormModal({
url: ApiEndpoints.stock_location_list, url: ApiEndpoints.stock_location_list,

View File

@@ -1,8 +1,13 @@
import { ApiEndpoints, ModelType } from '@lib/index'; import { ApiEndpoints, ModelType } from '@lib/index';
import type { TableFilter } from '@lib/types/Filters'; import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables'; import type { TableColumn } from '@lib/types/Tables';
import { t } from '@lingui/core/macro';
import { type ReactNode, useMemo } from 'react'; import { type ReactNode, useMemo } from 'react';
import { DescriptionColumn, ReferenceColumn } from '../ColumnRenderers'; import {
DescriptionColumn,
PartColumn,
ReferenceColumn
} from '../ColumnRenderers';
import { OrderStatusFilter, OutstandingFilter } from '../Filter'; import { OrderStatusFilter, OutstandingFilter } from '../Filter';
import ParametricDataTable from '../general/ParametricDataTable'; import ParametricDataTable from '../general/ParametricDataTable';
@@ -16,6 +21,10 @@ export default function BuildOrderParametricTable({
ReferenceColumn({ ReferenceColumn({
switchable: false switchable: false
}), }),
PartColumn({
part: 'part_detail',
title: t`Part`
}),
DescriptionColumn({ DescriptionColumn({
accessor: 'title' accessor: 'title'
}) })
@@ -33,6 +42,7 @@ export default function BuildOrderParametricTable({
customColumns={customColumns} customColumns={customColumns}
customFilters={customFilters} customFilters={customFilters}
queryParams={{ queryParams={{
part_detail: true,
...queryParams ...queryParams
}} }}
/> />

View File

@@ -0,0 +1,45 @@
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import type { TableColumn } from '@lib/types/Tables';
import { Group } from '@mantine/core';
import { type ReactNode, useMemo } from 'react';
import { ApiIcon } from '../../components/items/ApiIcon';
import { DescriptionColumn } from '../ColumnRenderers';
import ParametricDataTable from '../general/ParametricDataTable';
export default function StockLocationParametricTable({
queryParams
}: {
queryParams?: Record<string, any>;
}): ReactNode {
const customColumns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'name',
switchable: false,
render: (record: any) => (
<Group gap='xs'>
{record.icon && <ApiIcon name={record.icon} />}
{record.name}
</Group>
)
},
DescriptionColumn({}),
{
accessor: 'pathstring',
sortable: true
}
];
}, []);
return (
<ParametricDataTable
modelType={ModelType.stocklocation}
endpoint={ApiEndpoints.stock_location_list}
customColumns={customColumns}
queryParams={{
...queryParams
}}
/>
);
}