Stock location detail

This commit is contained in:
Oliver 2024-03-01 04:13:24 +00:00
parent 2ec7b74f6d
commit ca34e97ab8
4 changed files with 103 additions and 10 deletions

View File

@ -886,6 +886,7 @@ class LocationSerializer(InvenTree.serializers.InvenTreeTagModelSerializer):
'pathstring',
'path',
'items',
'sublocations',
'owner',
'icon',
'custom_icon',
@ -911,13 +912,18 @@ class LocationSerializer(InvenTree.serializers.InvenTreeTagModelSerializer):
def annotate_queryset(queryset):
"""Annotate extra information to the queryset."""
# 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
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)

View File

@ -39,6 +39,7 @@ import {
IconRulerMeasure,
IconShoppingCart,
IconShoppingCartHeart,
IconSitemap,
IconStack2,
IconStatusChange,
IconTag,
@ -138,7 +139,8 @@ const icons: { [key: string]: (props: TablerIconsProps) => React.JSX.Element } =
reference: IconHash,
website: IconWorld,
email: IconMail,
phone: IconPhone
phone: IconPhone,
sitemap: IconSitemap
};
/**

View File

@ -1,14 +1,17 @@
import { t } from '@lingui/macro';
import { LoadingOverlay, Stack, Text } from '@mantine/core';
import { IconPackages, IconSitemap } from '@tabler/icons-react';
import { LoadingOverlay, Skeleton, Stack, Text } from '@mantine/core';
import { IconInfoCircle, IconPackages, IconSitemap } from '@tabler/icons-react';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { StockLocationTree } from '../../components/nav/StockLocationTree';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { useInstance } from '../../hooks/UseInstance';
import { DetailsField, DetailsTable } from '../../tables/Details';
import { StockItemTable } from '../../tables/stock/StockItemTable';
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(() => {
return [
{
name: 'details',
label: t`Location Details`,
icon: <IconInfoCircle />,
content: detailsPanel
},
{
name: 'stock-items',
label: t`Stock Items`,

View File

@ -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
*/
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();
}
if (props.field_data.badge) {
if (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' }}>
<Suspense fallback={<Skeleton width={200} height={20} radius="xl" />}>
<span>
{value ? value : props.field_data.unit && '0'}{' '}
{value ? value : props.field_data?.unit && '0'}{' '}
{props.field_data.unit == true && props.unit}
</span>
</Suspense>
{props.field_data.user && (
<NameBadge pk={props.field_data.user} type="user" />
<NameBadge pk={props.field_data?.user} type="user" />
)}
</div>
);