mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-02 13:28:49 +00:00
[UI] Stock batch codes (#9145)
* Display batch code in stock item preview * Show batch code in stock operations modal * Refactor StockForms * More table refactoring
This commit is contained in:
parent
4cacf83294
commit
7098ac74d2
@ -1,5 +1,12 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Alert, FileInput, NumberInput, Stack, Switch } from '@mantine/core';
|
||||
import {
|
||||
Alert,
|
||||
FileInput,
|
||||
type MantineStyleProp,
|
||||
NumberInput,
|
||||
Stack,
|
||||
Switch
|
||||
} from '@mantine/core';
|
||||
import type { UseFormReturnType } from '@mantine/form';
|
||||
import { useId } from '@mantine/hooks';
|
||||
import { type ReactNode, useCallback, useEffect, useMemo } from 'react';
|
||||
@ -28,6 +35,12 @@ export type ApiFormFieldChoice = {
|
||||
display_name: string;
|
||||
};
|
||||
|
||||
// Define individual headers in a table field
|
||||
export type ApiFormFieldHeader = {
|
||||
title: string;
|
||||
style?: MantineStyleProp;
|
||||
};
|
||||
|
||||
/** Definition of the ApiForm field component.
|
||||
* - The 'name' attribute *must* be provided
|
||||
* - All other attributes are optional, and may be provided by the API
|
||||
@ -103,7 +116,7 @@ export type ApiFormFieldType = {
|
||||
onValueChange?: (value: any, record?: any) => void;
|
||||
adjustFilters?: (value: ApiFormAdjustFilterType) => any;
|
||||
addRow?: () => any;
|
||||
headers?: string[];
|
||||
headers?: ApiFormFieldHeader[];
|
||||
depends_on?: string[];
|
||||
};
|
||||
|
||||
|
@ -132,15 +132,21 @@ export function TableField({
|
||||
);
|
||||
|
||||
return (
|
||||
<Table highlightOnHover striped aria-label={`table-field-${field.name}`}>
|
||||
<Table
|
||||
highlightOnHover
|
||||
striped
|
||||
aria-label={`table-field-${field.name}`}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
{definition.headers?.map((header, index) => {
|
||||
return (
|
||||
<Table.Th
|
||||
key={`table-header-${identifierString(header)}-${index}`}
|
||||
key={`table-header-${identifierString(header.title)}-${index}`}
|
||||
style={header.style}
|
||||
>
|
||||
{header}
|
||||
{header.title}
|
||||
</Table.Th>
|
||||
);
|
||||
})}
|
||||
|
@ -63,10 +63,17 @@ export function RenderStockItem(
|
||||
quantity_string = `${t`Quantity`}: ${instance.quantity}`;
|
||||
}
|
||||
|
||||
let batch_string = '';
|
||||
|
||||
if (!!instance.batch) {
|
||||
batch_string = `${t`Batch`}: ${instance.batch}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<RenderInlineModel
|
||||
{...props}
|
||||
primary={instance.part_detail?.full_name}
|
||||
secondary={batch_string}
|
||||
suffix={<Text size='xs'>{quantity_string}</Text>}
|
||||
image={instance.part_detail?.thumbnail || instance.part_detail?.image}
|
||||
url={
|
||||
|
@ -276,7 +276,13 @@ export function useCompleteBuildOutputsForm({
|
||||
<BuildOutputFormRow props={row} record={record} key={record.pk} />
|
||||
);
|
||||
},
|
||||
headers: [t`Part`, t`Build Output`, t`Batch`, t`Status`]
|
||||
headers: [
|
||||
{ title: t`Part` },
|
||||
{ title: t`Build Output` },
|
||||
{ title: t`Batch` },
|
||||
{ title: t`Status` },
|
||||
{ title: '', style: { width: '50px' } }
|
||||
]
|
||||
},
|
||||
status_custom_key: {},
|
||||
location: {
|
||||
@ -344,7 +350,13 @@ export function useScrapBuildOutputsForm({
|
||||
<BuildOutputFormRow props={row} record={record} key={record.pk} />
|
||||
);
|
||||
},
|
||||
headers: [t`Part`, t`Stock Item`, t`Batch`, t`Status`]
|
||||
headers: [
|
||||
{ title: t`Part` },
|
||||
{ title: t`Stock Item` },
|
||||
{ title: t`Batch` },
|
||||
{ title: t`Status` },
|
||||
{ title: '', style: { width: '50px' } }
|
||||
]
|
||||
},
|
||||
location: {
|
||||
value: location,
|
||||
@ -392,7 +404,13 @@ export function useCancelBuildOutputsForm({
|
||||
<BuildOutputFormRow props={row} record={record} key={record.pk} />
|
||||
);
|
||||
},
|
||||
headers: [t`Part`, t`Stock Item`, t`Batch`, t`Status`]
|
||||
headers: [
|
||||
{ title: t`Part` },
|
||||
{ title: t`Stock Item` },
|
||||
{ title: t`Batch` },
|
||||
{ title: t`Status` },
|
||||
{ title: '', style: { width: '50px' } }
|
||||
]
|
||||
}
|
||||
};
|
||||
}, [outputs]);
|
||||
@ -522,7 +540,13 @@ export function useAllocateStockToBuildForm({
|
||||
items: {
|
||||
field_type: 'table',
|
||||
value: [],
|
||||
headers: [t`Part`, t`Allocated`, t`Stock Item`, t`Quantity`],
|
||||
headers: [
|
||||
{ title: t`Part`, style: { minWidth: '175px' } },
|
||||
{ title: t`Allocated`, style: { minWidth: '175px' } },
|
||||
{ title: t`Stock Item`, style: { width: '100%' } },
|
||||
{ title: t`Quantity`, style: { minWidth: '175px' } },
|
||||
{ title: '', style: { width: '50px' } }
|
||||
],
|
||||
modelRenderer: (row: TableFieldRowProps) => {
|
||||
// Find the matching record from the passed 'lineItems'
|
||||
const record =
|
||||
|
@ -523,9 +523,11 @@ function LineItemFormRow({
|
||||
onClick={() => open()}
|
||||
/>
|
||||
)}
|
||||
<RemoveRowButton onClick={() => props.removeFn(props.idx)} />
|
||||
</Flex>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<RemoveRowButton onClick={() => props.removeFn(props.idx)} />
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
{locationOpen && (
|
||||
<Table.Tr>
|
||||
@ -745,7 +747,14 @@ export function useReceiveLineItems(props: LineItemsForm) {
|
||||
/>
|
||||
);
|
||||
},
|
||||
headers: [t`Part`, t`SKU`, t`Received`, t`Quantity`, t`Actions`]
|
||||
headers: [
|
||||
{ title: t`Part`, style: { minWidth: '200px' } },
|
||||
{ title: t`SKU`, style: { minWidth: '200px' } },
|
||||
{ title: t`Received`, style: { minWidth: '200px' } },
|
||||
{ title: t`Quantity`, style: { width: '200px' } },
|
||||
{ title: t`Actions` },
|
||||
{ title: '', style: { width: '50px' } }
|
||||
]
|
||||
},
|
||||
location: {
|
||||
filters: {
|
||||
|
@ -234,7 +234,12 @@ export function useReceiveReturnOrderLineItems(
|
||||
/>
|
||||
);
|
||||
},
|
||||
headers: [t`Part`, t`Quantity`, t`Status`]
|
||||
headers: [
|
||||
{ title: t`Part`, style: { minWidth: '250px' } },
|
||||
{ title: t`Quantity`, style: { minWidth: '250px' } },
|
||||
{ title: t`Status`, style: { minWidth: '250px' } },
|
||||
{ title: '', style: { width: '50px' } }
|
||||
]
|
||||
},
|
||||
location: {
|
||||
filters: {
|
||||
|
@ -262,7 +262,12 @@ export function useAllocateToSalesOrderForm({
|
||||
items: {
|
||||
field_type: 'table',
|
||||
value: [],
|
||||
headers: [t`Part`, t`Allocated`, t`Stock Item`, t`Quantity`],
|
||||
headers: [
|
||||
{ title: t`Part`, style: { minWidth: '200px' } },
|
||||
{ title: t`Allocated`, style: { minWidth: '200px' } },
|
||||
{ title: t`Stock Item`, style: { width: '100%' } },
|
||||
{ title: t`Quantity`, style: { width: '200px' } }
|
||||
],
|
||||
modelRenderer: (row: TableFieldRowProps) => {
|
||||
const record =
|
||||
lineItems.find((item) => item.pk == row.item.line_item) ?? {};
|
||||
|
@ -549,6 +549,7 @@ function StockOperationsRow({
|
||||
<Table.Td>
|
||||
{record.location ? record.location_detail?.pathstring : '-'}
|
||||
</Table.Td>
|
||||
<Table.Td>{record.batch ? record.batch : '-'}</Table.Td>
|
||||
<Table.Td>
|
||||
<Group grow justify='space-between' wrap='nowrap'>
|
||||
<Text>{stockString}</Text>
|
||||
@ -697,7 +698,14 @@ function stockTransferFields(items: any[]): ApiFormFieldSet {
|
||||
/>
|
||||
);
|
||||
},
|
||||
headers: [t`Part`, t`Location`, t`Stock`, t`Move`, t`Actions`]
|
||||
headers: [
|
||||
{ title: t`Part` },
|
||||
{ title: t`Location` },
|
||||
{ title: t`Batch` },
|
||||
{ title: t`Stock` },
|
||||
{ title: t`Move`, style: { width: '200px' } },
|
||||
{ title: t`Actions` }
|
||||
]
|
||||
},
|
||||
location: {
|
||||
filters: {
|
||||
@ -734,7 +742,14 @@ function stockRemoveFields(items: any[]): ApiFormFieldSet {
|
||||
/>
|
||||
);
|
||||
},
|
||||
headers: [t`Part`, t`Location`, t`In Stock`, t`Remove`, t`Actions`]
|
||||
headers: [
|
||||
{ title: t`Part` },
|
||||
{ title: t`Location` },
|
||||
{ title: t`Batch` },
|
||||
{ title: t`In Stock` },
|
||||
{ title: t`Remove`, style: { width: '200px' } },
|
||||
{ title: t`Actions` }
|
||||
]
|
||||
},
|
||||
notes: {}
|
||||
};
|
||||
@ -766,7 +781,14 @@ function stockAddFields(items: any[]): ApiFormFieldSet {
|
||||
/>
|
||||
);
|
||||
},
|
||||
headers: [t`Part`, t`Location`, t`In Stock`, t`Add`, t`Actions`]
|
||||
headers: [
|
||||
{ title: t`Part` },
|
||||
{ title: t`Location` },
|
||||
{ title: t`Batch` },
|
||||
{ title: t`In Stock` },
|
||||
{ title: t`Add`, style: { width: '200px' } },
|
||||
{ title: t`Actions` }
|
||||
]
|
||||
},
|
||||
notes: {}
|
||||
};
|
||||
@ -795,7 +817,14 @@ function stockCountFields(items: any[]): ApiFormFieldSet {
|
||||
/>
|
||||
);
|
||||
},
|
||||
headers: [t`Part`, t`Location`, t`In Stock`, t`Count`, t`Actions`]
|
||||
headers: [
|
||||
{ title: t`Part` },
|
||||
{ title: t`Location` },
|
||||
{ title: t`Batch` },
|
||||
{ title: t`In Stock` },
|
||||
{ title: t`Count`, style: { width: '200px' } },
|
||||
{ title: t`Actions` }
|
||||
]
|
||||
},
|
||||
notes: {}
|
||||
};
|
||||
@ -826,7 +855,13 @@ function stockChangeStatusFields(items: any[]): ApiFormFieldSet {
|
||||
/>
|
||||
);
|
||||
},
|
||||
headers: [t`Part`, t`Location`, t`In Stock`, t`Actions`]
|
||||
headers: [
|
||||
{ title: t`Part` },
|
||||
{ title: t`Location` },
|
||||
{ title: t`Batch` },
|
||||
{ title: t`In Stock` },
|
||||
{ title: '', style: { width: '50px' } }
|
||||
]
|
||||
},
|
||||
status: {},
|
||||
note: {}
|
||||
@ -862,7 +897,13 @@ function stockMergeFields(items: any[]): ApiFormFieldSet {
|
||||
/>
|
||||
);
|
||||
},
|
||||
headers: [t`Part`, t`Location`, t`In Stock`, t`Actions`]
|
||||
headers: [
|
||||
{ title: t`Part` },
|
||||
{ title: t`Location` },
|
||||
{ title: t`Batch` },
|
||||
{ title: t`In Stock` },
|
||||
{ title: t`Actions` }
|
||||
]
|
||||
},
|
||||
location: {
|
||||
default: items[0]?.part_detail.default_location,
|
||||
@ -904,7 +945,13 @@ function stockAssignFields(items: any[]): ApiFormFieldSet {
|
||||
/>
|
||||
);
|
||||
},
|
||||
headers: [t`Part`, t`Location`, t`In Stock`, t`Actions`]
|
||||
headers: [
|
||||
{ title: t`Part` },
|
||||
{ title: t`Location` },
|
||||
{ title: t`Batch` },
|
||||
{ title: t`In Stock` },
|
||||
{ title: '', style: { width: '50px' } }
|
||||
]
|
||||
},
|
||||
customer: {
|
||||
filters: {
|
||||
@ -942,7 +989,13 @@ function stockDeleteFields(items: any[]): ApiFormFieldSet {
|
||||
/>
|
||||
);
|
||||
},
|
||||
headers: [t`Part`, t`Location`, t`In Stock`, t`Actions`]
|
||||
headers: [
|
||||
{ title: t`Part` },
|
||||
{ title: t`Location` },
|
||||
{ title: t`Batch` },
|
||||
{ title: t`In Stock` },
|
||||
{ title: '', style: { width: '50px' } }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -100,7 +100,12 @@ export function selectionListFields(): ApiFormFieldSet {
|
||||
description: t`List of entries to choose from`,
|
||||
field_type: 'table',
|
||||
value: [],
|
||||
headers: [t`Value`, t`Label`, t`Description`, t`Active`],
|
||||
headers: [
|
||||
{ title: t`Value` },
|
||||
{ title: t`Label` },
|
||||
{ title: t`Description` },
|
||||
{ title: t`Active` }
|
||||
],
|
||||
modelRenderer: (row: TableFieldRowProps) => (
|
||||
<BuildAllocateLineRow props={row} />
|
||||
),
|
||||
|
Loading…
x
Reference in New Issue
Block a user