mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 13:05:42 +00:00
Stock location detail
This commit is contained in:
@ -886,6 +886,7 @@ class LocationSerializer(InvenTree.serializers.InvenTreeTagModelSerializer):
|
|||||||
'pathstring',
|
'pathstring',
|
||||||
'path',
|
'path',
|
||||||
'items',
|
'items',
|
||||||
|
'sublocations',
|
||||||
'owner',
|
'owner',
|
||||||
'icon',
|
'icon',
|
||||||
'custom_icon',
|
'custom_icon',
|
||||||
@ -911,13 +912,18 @@ class LocationSerializer(InvenTree.serializers.InvenTreeTagModelSerializer):
|
|||||||
def annotate_queryset(queryset):
|
def annotate_queryset(queryset):
|
||||||
"""Annotate extra information to the queryset."""
|
"""Annotate extra information to the queryset."""
|
||||||
# Annotate the number of stock items which exist in this category (including subcategories)
|
# Annotate the number of stock items which exist in this category (including subcategories)
|
||||||
queryset = queryset.annotate(items=stock.filters.annotate_location_items())
|
queryset = queryset.annotate(
|
||||||
|
items=stock.filters.annotate_location_items(),
|
||||||
|
sublocations=stock.filters.annotate_sub_locations(),
|
||||||
|
)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||||
|
|
||||||
items = serializers.IntegerField(read_only=True)
|
items = serializers.IntegerField(read_only=True, label=_('Stock Items'))
|
||||||
|
|
||||||
|
sublocations = serializers.IntegerField(read_only=True, label=_('Sublocations'))
|
||||||
|
|
||||||
level = serializers.IntegerField(read_only=True)
|
level = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ import {
|
|||||||
IconRulerMeasure,
|
IconRulerMeasure,
|
||||||
IconShoppingCart,
|
IconShoppingCart,
|
||||||
IconShoppingCartHeart,
|
IconShoppingCartHeart,
|
||||||
|
IconSitemap,
|
||||||
IconStack2,
|
IconStack2,
|
||||||
IconStatusChange,
|
IconStatusChange,
|
||||||
IconTag,
|
IconTag,
|
||||||
@ -138,7 +139,8 @@ const icons: { [key: string]: (props: TablerIconsProps) => React.JSX.Element } =
|
|||||||
reference: IconHash,
|
reference: IconHash,
|
||||||
website: IconWorld,
|
website: IconWorld,
|
||||||
email: IconMail,
|
email: IconMail,
|
||||||
phone: IconPhone
|
phone: IconPhone,
|
||||||
|
sitemap: IconSitemap
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { LoadingOverlay, Stack, Text } from '@mantine/core';
|
import { LoadingOverlay, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
import { IconPackages, IconSitemap } from '@tabler/icons-react';
|
import { IconInfoCircle, IconPackages, IconSitemap } from '@tabler/icons-react';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
import { StockLocationTree } from '../../components/nav/StockLocationTree';
|
import { StockLocationTree } from '../../components/nav/StockLocationTree';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
|
import { DetailsField, DetailsTable } from '../../tables/Details';
|
||||||
import { StockItemTable } from '../../tables/stock/StockItemTable';
|
import { StockItemTable } from '../../tables/stock/StockItemTable';
|
||||||
import { StockLocationTable } from '../../tables/stock/StockLocationTable';
|
import { StockLocationTable } from '../../tables/stock/StockLocationTable';
|
||||||
|
|
||||||
@ -35,8 +38,90 @@ export default function Stock() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const detailsPanel = useMemo(() => {
|
||||||
|
if (id && instanceQuery.isFetching) {
|
||||||
|
return <Skeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
|
let left: DetailsField[] = [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'name',
|
||||||
|
label: t`Name`,
|
||||||
|
copy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'pathstring',
|
||||||
|
label: t`Path`,
|
||||||
|
icon: 'sitemap',
|
||||||
|
copy: true,
|
||||||
|
hidden: !id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'description',
|
||||||
|
label: t`Description`,
|
||||||
|
copy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
name: 'parent',
|
||||||
|
model_field: 'name',
|
||||||
|
icon: 'location',
|
||||||
|
label: t`Parent Location`,
|
||||||
|
model: ModelType.stocklocation,
|
||||||
|
hidden: !location?.parent
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let right: DetailsField[] = [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'items',
|
||||||
|
icon: 'stock',
|
||||||
|
label: t`Stock Items`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'sublocations',
|
||||||
|
icon: 'location',
|
||||||
|
label: t`Sublocations`,
|
||||||
|
hidden: !location?.sublocations
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'boolean',
|
||||||
|
name: 'structural',
|
||||||
|
label: t`Structural`,
|
||||||
|
icon: 'sitemap'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'boolean',
|
||||||
|
name: 'external',
|
||||||
|
label: t`External`
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ItemDetailsGrid>
|
||||||
|
{id && location ? (
|
||||||
|
<DetailsTable item={location} fields={left} />
|
||||||
|
) : (
|
||||||
|
<Text>{t`Top level stock location`}</Text>
|
||||||
|
)}
|
||||||
|
{id && location && <DetailsTable item={location} fields={right} />}
|
||||||
|
</ItemDetailsGrid>
|
||||||
|
);
|
||||||
|
}, [location, instanceQuery]);
|
||||||
|
|
||||||
const locationPanels: PanelType[] = useMemo(() => {
|
const locationPanels: PanelType[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
name: 'details',
|
||||||
|
label: t`Location Details`,
|
||||||
|
icon: <IconInfoCircle />,
|
||||||
|
content: detailsPanel
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'stock-items',
|
name: 'stock-items',
|
||||||
label: t`Stock Items`,
|
label: t`Stock Items`,
|
||||||
|
@ -276,13 +276,13 @@ function NameBadge({ pk, type }: { pk: string | number; type: BadgeType }) {
|
|||||||
* If user is defined, a badge is rendered in addition to main value
|
* If user is defined, a badge is rendered in addition to main value
|
||||||
*/
|
*/
|
||||||
function TableStringValue(props: FieldProps) {
|
function TableStringValue(props: FieldProps) {
|
||||||
let value = props.field_value;
|
let value = props?.field_value ?? {};
|
||||||
|
|
||||||
if (props.field_data.value_formatter) {
|
if (props.field_data?.value_formatter) {
|
||||||
value = props.field_data.value_formatter();
|
value = props.field_data.value_formatter();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.field_data.badge) {
|
if (props.field_data?.badge) {
|
||||||
return <NameBadge pk={value} type={props.field_data.badge} />;
|
return <NameBadge pk={value} type={props.field_data.badge} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,12 +290,12 @@ function TableStringValue(props: FieldProps) {
|
|||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<Suspense fallback={<Skeleton width={200} height={20} radius="xl" />}>
|
<Suspense fallback={<Skeleton width={200} height={20} radius="xl" />}>
|
||||||
<span>
|
<span>
|
||||||
{value ? value : props.field_data.unit && '0'}{' '}
|
{value ? value : props.field_data?.unit && '0'}{' '}
|
||||||
{props.field_data.unit == true && props.unit}
|
{props.field_data.unit == true && props.unit}
|
||||||
</span>
|
</span>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
{props.field_data.user && (
|
{props.field_data.user && (
|
||||||
<NameBadge pk={props.field_data.user} type="user" />
|
<NameBadge pk={props.field_data?.user} type="user" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user