mirror of
https://github.com/inventree/InvenTree.git
synced 2026-02-13 01:38:03 +00:00
Display more output information in "build allocated stock" table (#11276)
* Add "install_into_detail" to BuildItem serializer * Enhance the "resolveItem" function * Add "StockColumn" renderer * Fix output column for BuildAllocatedStockTable * Replace column in stock item table * More column refactoring * Bump API version * Add InvenTreeOutputOption descriptions * Prefetch for better API performance * Updated playwright testing
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 451
|
||||
INVENTREE_API_VERSION = 452
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v452 -> 2026-02-10 : https://github.com/inventree/InvenTree/pull/11276
|
||||
- Adds "install_into_detail" field to the BuildItem API endpoint
|
||||
|
||||
v451 -> 2026-02-10 : https://github.com/inventree/InvenTree/pull/11277
|
||||
- Adds sorting to multiple part related endpoints (part, IPN, ...)
|
||||
|
||||
|
||||
@@ -808,13 +808,17 @@ class BuildCancel(BuildOrderContextMixin, CreateAPI):
|
||||
serializer_class = build.serializers.BuildCancelSerializer
|
||||
|
||||
|
||||
class BuildItemDetail(RetrieveUpdateDestroyAPI):
|
||||
"""API endpoint for detail view of a BuildItem object."""
|
||||
class BuildItemMixin:
|
||||
"""Mixin class for BuildItem API endpoints."""
|
||||
|
||||
queryset = BuildItem.objects.all()
|
||||
queryset = BuildItem.objects.all().prefetch_related('stock_item__location')
|
||||
serializer_class = build.serializers.BuildItemSerializer
|
||||
|
||||
|
||||
class BuildItemDetail(BuildItemMixin, RetrieveUpdateDestroyAPI):
|
||||
"""API endpoint for detail view of a BuildItem object."""
|
||||
|
||||
|
||||
class BuildItemFilter(FilterSet):
|
||||
"""Custom filterset for the BuildItemList API endpoint."""
|
||||
|
||||
@@ -899,16 +903,45 @@ class BuildItemOutputOptions(OutputConfiguration):
|
||||
"""Output options for BuildItem endpoint."""
|
||||
|
||||
OPTIONS = [
|
||||
InvenTreeOutputOption('part_detail'),
|
||||
InvenTreeOutputOption('location_detail'),
|
||||
InvenTreeOutputOption('stock_detail'),
|
||||
InvenTreeOutputOption('build_detail'),
|
||||
InvenTreeOutputOption('supplier_part_detail'),
|
||||
InvenTreeOutputOption(
|
||||
'part_detail',
|
||||
default=False,
|
||||
description='Include detailed information about the part associated with this build item.',
|
||||
),
|
||||
InvenTreeOutputOption(
|
||||
'location_detail',
|
||||
default=False,
|
||||
description='Include detailed information about the location of the allocated stock item.',
|
||||
),
|
||||
InvenTreeOutputOption(
|
||||
'stock_detail',
|
||||
default=False,
|
||||
description='Include detailed information about the allocated stock item.',
|
||||
),
|
||||
InvenTreeOutputOption(
|
||||
'build_detail',
|
||||
default=False,
|
||||
description='Include detailed information about the associated build order.',
|
||||
),
|
||||
InvenTreeOutputOption(
|
||||
'supplier_part_detail',
|
||||
default=False,
|
||||
description='Include detailed information about the supplier part associated with this build item.',
|
||||
),
|
||||
InvenTreeOutputOption(
|
||||
'install_into_detail',
|
||||
default=False,
|
||||
description='Include detailed information about the build output for this build item.',
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class BuildItemList(
|
||||
DataExportViewMixin, OutputOptionsMixin, BulkDeleteMixin, ListCreateAPI
|
||||
BuildItemMixin,
|
||||
DataExportViewMixin,
|
||||
OutputOptionsMixin,
|
||||
BulkDeleteMixin,
|
||||
ListCreateAPI,
|
||||
):
|
||||
"""API endpoint for accessing a list of BuildItem objects.
|
||||
|
||||
@@ -917,8 +950,6 @@ class BuildItemList(
|
||||
"""
|
||||
|
||||
output_options = BuildItemOutputOptions
|
||||
queryset = BuildItem.objects.all()
|
||||
serializer_class = build.serializers.BuildItemSerializer
|
||||
filterset_class = BuildItemFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
|
||||
|
||||
@@ -1179,6 +1179,7 @@ class BuildItemSerializer(
|
||||
'part_detail',
|
||||
'stock_item_detail',
|
||||
'supplier_part_detail',
|
||||
'install_into_detail',
|
||||
# The following fields are only used for data export
|
||||
'bom_reference',
|
||||
'bom_part_id',
|
||||
@@ -1244,6 +1245,21 @@ class BuildItemSerializer(
|
||||
],
|
||||
)
|
||||
|
||||
install_into_detail = enable_filter(
|
||||
StockItemSerializer(
|
||||
source='install_into',
|
||||
read_only=True,
|
||||
allow_null=True,
|
||||
label=_('Install Into'),
|
||||
part_detail=False,
|
||||
location_detail=False,
|
||||
supplier_part_detail=False,
|
||||
path_detail=False,
|
||||
),
|
||||
False,
|
||||
prefetch_fields=['install_into', 'install_into__part'],
|
||||
)
|
||||
|
||||
location = serializers.PrimaryKeyRelatedField(
|
||||
label=_('Location'), source='stock_item.location', many=False, read_only=True
|
||||
)
|
||||
|
||||
@@ -29,6 +29,11 @@ export function isTrue(value: any): boolean {
|
||||
* Allows for retrieval of nested items in an object.
|
||||
*/
|
||||
export function resolveItem(obj: any, path: string): any {
|
||||
// Return the top-level object if no path is provided
|
||||
if (path == null || path === '') {
|
||||
return obj;
|
||||
}
|
||||
|
||||
const properties = path.split('.');
|
||||
return properties.reduce((prev, curr) => prev?.[curr], obj);
|
||||
}
|
||||
|
||||
@@ -93,10 +93,10 @@ export function PartColumn(props: PartColumnProps): TableColumn {
|
||||
switchable: false,
|
||||
minWidth: '175px',
|
||||
render: (record: any) => {
|
||||
const part =
|
||||
props.part === ''
|
||||
? record
|
||||
: resolveItem(record, props.part ?? props.accessor ?? 'part_detail');
|
||||
const part = resolveItem(
|
||||
record,
|
||||
props.part ?? props.accessor ?? 'part_detail'
|
||||
);
|
||||
|
||||
return RenderPartColumn({
|
||||
part: part,
|
||||
@@ -107,6 +107,174 @@ export function PartColumn(props: PartColumnProps): TableColumn {
|
||||
};
|
||||
}
|
||||
|
||||
export type StockColumnProps = TableColumnProps & {
|
||||
nullMessage?: string | ReactNode;
|
||||
};
|
||||
|
||||
// Render a StockItem instance within a table
|
||||
export function StockColumn(props: StockColumnProps): TableColumn {
|
||||
return {
|
||||
accessor: props.accessor ?? 'stock_item',
|
||||
title: t`Stock Item`,
|
||||
...props,
|
||||
render: (record: any) => {
|
||||
const stock_item =
|
||||
resolveItem(record, props.accessor ?? 'stock_item_detail') ?? {};
|
||||
const part = stock_item.part_detail ?? {};
|
||||
|
||||
const quantity = stock_item.quantity ?? 0;
|
||||
const allocated = stock_item.allocated ?? 0;
|
||||
const available = quantity - allocated;
|
||||
|
||||
const extra: ReactNode[] = [];
|
||||
let color = undefined;
|
||||
let text = formatDecimal(quantity);
|
||||
|
||||
// Handle case where stock item detail is not provided
|
||||
if (!stock_item || !stock_item.pk) {
|
||||
return props.nullMessage ?? '-';
|
||||
}
|
||||
|
||||
// Override with serial number if available
|
||||
if (stock_item.serial && quantity == 1) {
|
||||
text = `# ${stock_item.serial}`;
|
||||
}
|
||||
|
||||
if (record.is_building) {
|
||||
color = 'blue';
|
||||
extra.push(
|
||||
<Text
|
||||
key='production'
|
||||
size='sm'
|
||||
>{t`This stock item is in production`}</Text>
|
||||
);
|
||||
} else if (record.sales_order) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='sales-order'
|
||||
size='sm'
|
||||
>{t`This stock item has been assigned to a sales order`}</Text>
|
||||
);
|
||||
} else if (record.customer) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='customer'
|
||||
size='sm'
|
||||
>{t`This stock item has been assigned to a customer`}</Text>
|
||||
);
|
||||
} else if (record.belongs_to) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='belongs-to'
|
||||
size='sm'
|
||||
>{t`This stock item is installed in another stock item`}</Text>
|
||||
);
|
||||
} else if (record.consumed_by) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='consumed-by'
|
||||
size='sm'
|
||||
>{t`This stock item has been consumed by a build order`}</Text>
|
||||
);
|
||||
} else if (!record.in_stock) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='unavailable'
|
||||
size='sm'
|
||||
>{t`This stock item is unavailable`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (record.expired) {
|
||||
extra.push(
|
||||
<Text key='expired' size='sm'>{t`This stock item has expired`}</Text>
|
||||
);
|
||||
} else if (record.stale) {
|
||||
extra.push(
|
||||
<Text key='stale' size='sm'>{t`This stock item is stale`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (record.in_stock) {
|
||||
if (allocated > 0) {
|
||||
if (allocated > quantity) {
|
||||
color = 'red';
|
||||
extra.push(
|
||||
<Text
|
||||
key='over-allocated'
|
||||
size='sm'
|
||||
>{t`This stock item is over-allocated`}</Text>
|
||||
);
|
||||
} else if (allocated == quantity) {
|
||||
color = 'orange';
|
||||
extra.push(
|
||||
<Text
|
||||
key='fully-allocated'
|
||||
size='sm'
|
||||
>{t`This stock item is fully allocated`}</Text>
|
||||
);
|
||||
} else {
|
||||
extra.push(
|
||||
<Text
|
||||
key='partially-allocated'
|
||||
size='sm'
|
||||
>{t`This stock item is partially allocated`}</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (available != quantity) {
|
||||
if (available > 0) {
|
||||
extra.push(
|
||||
<Text key='available' size='sm' c='orange'>
|
||||
{`${t`Available`}: ${formatDecimal(available)}`}
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
extra.push(
|
||||
<Text
|
||||
key='no-stock'
|
||||
size='sm'
|
||||
c='red'
|
||||
>{t`No stock available`}</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (quantity <= 0) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='depleted'
|
||||
size='sm'
|
||||
>{t`This stock item has been depleted`}</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!record.in_stock) {
|
||||
color = 'red';
|
||||
}
|
||||
|
||||
return (
|
||||
<TableHoverCard
|
||||
value={
|
||||
<Group gap='xs' justify='left' wrap='nowrap'>
|
||||
<Text c={color}>{text}</Text>
|
||||
{part.units && (
|
||||
<Text size='xs' c={color}>
|
||||
[{part.units}]
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
}
|
||||
title={t`Stock Information`}
|
||||
extra={extra}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function CompanyColumn({
|
||||
company
|
||||
}: {
|
||||
|
||||
@@ -25,7 +25,8 @@ import {
|
||||
LocationColumn,
|
||||
PartColumn,
|
||||
ReferenceColumn,
|
||||
StatusColumn
|
||||
StatusColumn,
|
||||
StockColumn
|
||||
} from '../ColumnRenderers';
|
||||
import { IncludeVariantsFilter, StockLocationFilter } from '../Filter';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
@@ -142,11 +143,11 @@ export default function BuildAllocatedStockTable({
|
||||
switchable: true,
|
||||
sortable: true
|
||||
}),
|
||||
{
|
||||
accessor: 'install_into',
|
||||
StockColumn({
|
||||
accessor: 'install_into_detail',
|
||||
title: t`Build Output`,
|
||||
sortable: true
|
||||
},
|
||||
sortable: false
|
||||
}),
|
||||
{
|
||||
accessor: 'sku',
|
||||
title: t`Supplier Part`,
|
||||
@@ -307,6 +308,7 @@ export default function BuildAllocatedStockTable({
|
||||
part_detail: showPartInfo ?? false,
|
||||
location_detail: true,
|
||||
stock_detail: true,
|
||||
install_into_detail: true,
|
||||
supplier_detail: true
|
||||
},
|
||||
enableBulkDelete: allowEdit && user.hasDeleteRole(UserRoles.build),
|
||||
|
||||
@@ -36,7 +36,8 @@ import {
|
||||
PartColumn,
|
||||
ProjectCodeColumn,
|
||||
ReferenceColumn,
|
||||
StatusColumn
|
||||
StatusColumn,
|
||||
StockColumn
|
||||
} from '../ColumnRenderers';
|
||||
import { StatusFilterOptions } from '../Filter';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
@@ -121,20 +122,12 @@ export default function ReturnOrderLineItemTable({
|
||||
DescriptionColumn({
|
||||
accessor: 'part_detail.description'
|
||||
}),
|
||||
{
|
||||
accessor: 'item_detail.serial',
|
||||
title: t`Quantity`,
|
||||
StockColumn({
|
||||
accessor: 'item_detail',
|
||||
switchable: false,
|
||||
sortable: true,
|
||||
ordering: 'stock',
|
||||
render: (record: any) => {
|
||||
if (record.item_detail.serial && record.quantity == 1) {
|
||||
return `# ${record.item_detail.serial}`;
|
||||
} else {
|
||||
return record.quantity;
|
||||
}
|
||||
}
|
||||
},
|
||||
ordering: 'stock'
|
||||
}),
|
||||
StatusColumn({
|
||||
model: ModelType.stockitem,
|
||||
sortable: false,
|
||||
|
||||
@@ -8,7 +8,6 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { UserRoles } from '@lib/enums/Roles';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { formatDecimal } from '@lib/functions/Formatting';
|
||||
import type { TableColumn } from '@lib/types/Tables';
|
||||
import {
|
||||
useStockItemInstallFields,
|
||||
@@ -17,7 +16,7 @@ import {
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { PartColumn, StatusColumn } from '../ColumnRenderers';
|
||||
import { PartColumn, StatusColumn, StockColumn } from '../ColumnRenderers';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
|
||||
export default function InstalledItemsTable({
|
||||
@@ -62,19 +61,11 @@ export default function InstalledItemsTable({
|
||||
PartColumn({
|
||||
part: 'part_detail'
|
||||
}),
|
||||
{
|
||||
accessor: 'quantity',
|
||||
switchable: false,
|
||||
render: (record: any) => {
|
||||
let text = formatDecimal(record.quantity);
|
||||
|
||||
if (record.serial && record.quantity == 1) {
|
||||
text = `# ${record.serial}`;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
},
|
||||
StockColumn({
|
||||
accessor: '',
|
||||
title: t`Stock Item`,
|
||||
sortable: false
|
||||
}),
|
||||
{
|
||||
accessor: 'batch',
|
||||
switchable: false
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Group, Text } from '@mantine/core';
|
||||
import { type ReactNode, useMemo, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ActionButton } from '@lib/components/ActionButton';
|
||||
@@ -14,11 +13,7 @@ import type { TableFilter } from '@lib/types/Filters';
|
||||
import type { StockOperationProps } from '@lib/types/Forms';
|
||||
import type { TableColumn } from '@lib/types/Tables';
|
||||
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
|
||||
import {
|
||||
formatCurrency,
|
||||
formatDecimal,
|
||||
formatPriceRange
|
||||
} from '../../defaults/formatters';
|
||||
import { formatCurrency, formatPriceRange } from '../../defaults/formatters';
|
||||
import { useStockFields } from '../../forms/StockForms';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
@@ -31,7 +26,8 @@ import {
|
||||
DescriptionColumn,
|
||||
LocationColumn,
|
||||
PartColumn,
|
||||
StatusColumn
|
||||
StatusColumn,
|
||||
StockColumn
|
||||
} from '../ColumnRenderers';
|
||||
import {
|
||||
BatchFilter,
|
||||
@@ -47,7 +43,6 @@ import {
|
||||
SupplierFilter
|
||||
} from '../Filter';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { TableHoverCard } from '../TableHoverCard';
|
||||
|
||||
/**
|
||||
* Construct a list of columns for the stock item table
|
||||
@@ -79,161 +74,12 @@ function stockItemTableColumns({
|
||||
DescriptionColumn({
|
||||
accessor: 'part_detail.description'
|
||||
}),
|
||||
{
|
||||
accessor: 'quantity',
|
||||
ordering: 'stock',
|
||||
sortable: true,
|
||||
StockColumn({
|
||||
accessor: '',
|
||||
title: t`Stock`,
|
||||
render: (record: any) => {
|
||||
// TODO: Push this out into a custom renderer
|
||||
const quantity = record?.quantity ?? 0;
|
||||
const allocated = record?.allocated ?? 0;
|
||||
const available = quantity - allocated;
|
||||
let text = formatDecimal(quantity);
|
||||
const part = record?.part_detail ?? {};
|
||||
const extra: ReactNode[] = [];
|
||||
let color = undefined;
|
||||
|
||||
if (record.serial && quantity == 1) {
|
||||
text = `# ${record.serial}`;
|
||||
}
|
||||
|
||||
if (record.is_building) {
|
||||
color = 'blue';
|
||||
extra.push(
|
||||
<Text
|
||||
key='production'
|
||||
size='sm'
|
||||
>{t`This stock item is in production`}</Text>
|
||||
);
|
||||
} else if (record.sales_order) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='sales-order'
|
||||
size='sm'
|
||||
>{t`This stock item has been assigned to a sales order`}</Text>
|
||||
);
|
||||
} else if (record.customer) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='customer'
|
||||
size='sm'
|
||||
>{t`This stock item has been assigned to a customer`}</Text>
|
||||
);
|
||||
} else if (record.belongs_to) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='belongs-to'
|
||||
size='sm'
|
||||
>{t`This stock item is installed in another stock item`}</Text>
|
||||
);
|
||||
} else if (record.consumed_by) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='consumed-by'
|
||||
size='sm'
|
||||
>{t`This stock item has been consumed by a build order`}</Text>
|
||||
);
|
||||
} else if (!record.in_stock) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='unavailable'
|
||||
size='sm'
|
||||
>{t`This stock item is unavailable`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (record.expired) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='expired'
|
||||
size='sm'
|
||||
>{t`This stock item has expired`}</Text>
|
||||
);
|
||||
} else if (record.stale) {
|
||||
extra.push(
|
||||
<Text key='stale' size='sm'>{t`This stock item is stale`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (record.in_stock) {
|
||||
if (allocated > 0) {
|
||||
if (allocated > quantity) {
|
||||
color = 'red';
|
||||
extra.push(
|
||||
<Text
|
||||
key='over-allocated'
|
||||
size='sm'
|
||||
>{t`This stock item is over-allocated`}</Text>
|
||||
);
|
||||
} else if (allocated == quantity) {
|
||||
color = 'orange';
|
||||
extra.push(
|
||||
<Text
|
||||
key='fully-allocated'
|
||||
size='sm'
|
||||
>{t`This stock item is fully allocated`}</Text>
|
||||
);
|
||||
} else {
|
||||
extra.push(
|
||||
<Text
|
||||
key='partially-allocated'
|
||||
size='sm'
|
||||
>{t`This stock item is partially allocated`}</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (available != quantity) {
|
||||
if (available > 0) {
|
||||
extra.push(
|
||||
<Text key='available' size='sm' c='orange'>
|
||||
{`${t`Available`}: ${formatDecimal(available)}`}
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
extra.push(
|
||||
<Text
|
||||
key='no-stock'
|
||||
size='sm'
|
||||
c='red'
|
||||
>{t`No stock available`}</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (quantity <= 0) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='depleted'
|
||||
size='sm'
|
||||
>{t`This stock item has been depleted`}</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!record.in_stock) {
|
||||
color = 'red';
|
||||
}
|
||||
|
||||
return (
|
||||
<TableHoverCard
|
||||
value={
|
||||
<Group gap='xs' justify='left' wrap='nowrap'>
|
||||
<Text c={color}>{text}</Text>
|
||||
{part.units && (
|
||||
<Text size='xs' c={color}>
|
||||
[{part.units}]
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
}
|
||||
title={t`Stock Information`}
|
||||
extra={extra}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
sortable: true,
|
||||
ordering: 'stock'
|
||||
}),
|
||||
StatusColumn({ model: ModelType.stockitem }),
|
||||
{
|
||||
accessor: 'batch',
|
||||
|
||||
@@ -24,7 +24,12 @@ import {
|
||||
} from '../../components/render/Stock';
|
||||
import { RenderUser } from '../../components/render/User';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { DateColumn, DescriptionColumn, PartColumn } from '../ColumnRenderers';
|
||||
import {
|
||||
DateColumn,
|
||||
DescriptionColumn,
|
||||
PartColumn,
|
||||
StockColumn
|
||||
} from '../ColumnRenderers';
|
||||
import {
|
||||
IncludeVariantsFilter,
|
||||
MaxDateFilter,
|
||||
@@ -241,29 +246,16 @@ export function StockTrackingTable({
|
||||
switchable: true,
|
||||
hidden: !partId
|
||||
},
|
||||
{
|
||||
accessor: 'item',
|
||||
StockColumn({
|
||||
title: t`Stock Item`,
|
||||
accessor: 'item_detail',
|
||||
nullMessage: (
|
||||
<Text size='sm' c='red'>{t`Stock item no longer exists`}</Text>
|
||||
),
|
||||
sortable: false,
|
||||
switchable: false,
|
||||
hidden: !partId,
|
||||
render: (record: any) => {
|
||||
const item = record.item_detail;
|
||||
if (!item) {
|
||||
return (
|
||||
<Text
|
||||
c='red'
|
||||
size='xs'
|
||||
fs='italic'
|
||||
>{t`Stock item no longer exists`}</Text>
|
||||
);
|
||||
} else if (item.serial && item.quantity == 1) {
|
||||
return `${t`Serial`} #${item.serial}`;
|
||||
} else {
|
||||
return `${t`Item ID`} ${item.pk}`;
|
||||
}
|
||||
}
|
||||
},
|
||||
hidden: !partId
|
||||
}),
|
||||
DescriptionColumn({
|
||||
accessor: 'label'
|
||||
}),
|
||||
|
||||
@@ -470,8 +470,8 @@ test('Stock - Tracking', async ({ browser }) => {
|
||||
.getByRole('cell', { name: 'Thumbnail Blue Widget' })
|
||||
.first()
|
||||
.waitFor();
|
||||
await page.getByRole('cell', { name: 'Item ID 232' }).first().waitFor();
|
||||
await page.getByRole('cell', { name: 'Serial #116' }).first().waitFor();
|
||||
|
||||
await page.getByText('# 162').first().waitFor();
|
||||
});
|
||||
|
||||
test('Stock - Location', async ({ browser }) => {
|
||||
|
||||
Reference in New Issue
Block a user