2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 11:36:44 +00:00

Batch code backport (#9138)

* Batch code fix (#9123)

* Fix batch code assignment when receiving items

* Add playwright tests

* Harden playwright tests

* Refactoring

* Handle undefined values

* Fix conflicts
This commit is contained in:
Oliver 2025-02-22 11:52:16 +11:00 committed by GitHub
parent 8a2fce9c36
commit cd41ca2a87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 81 additions and 62 deletions

View File

@ -83,6 +83,26 @@ export function getStatusCodes(type: ModelType | string) {
return statusCodes; return statusCodes;
} }
/**
* Return a list of status codes select options for a given model type
* returns an array of objects with keys "value" and "display_name"
*
*/
export function getStatusCodeOptions(type: ModelType | string): any[] {
const statusCodes = getStatusCodes(type);
if (!statusCodes) {
return [];
}
return Object.values(statusCodes?.values ?? []).map((entry) => {
return {
value: entry.key,
display_name: entry.label
};
});
}
/* /*
* Return the name of a status code, based on the key * Return the name of a status code, based on the key
*/ */

View File

@ -22,10 +22,7 @@ import {
IconUser, IconUser,
IconUsers IconUsers
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { api } from '../App';
import { ActionButton } from '../components/buttons/ActionButton'; import { ActionButton } from '../components/buttons/ActionButton';
import RemoveRowButton from '../components/buttons/RemoveRowButton'; import RemoveRowButton from '../components/buttons/RemoveRowButton';
import { StandaloneField } from '../components/forms/StandaloneField'; import { StandaloneField } from '../components/forms/StandaloneField';
@ -40,6 +37,7 @@ import {
import { Thumbnail } from '../components/images/Thumbnail'; import { Thumbnail } from '../components/images/Thumbnail';
import { ProgressBar } from '../components/items/ProgressBar'; import { ProgressBar } from '../components/items/ProgressBar';
import { StylishText } from '../components/items/StylishText'; import { StylishText } from '../components/items/StylishText';
import { getStatusCodeOptions } from '../components/render/StatusRenderer';
import { ApiEndpoints } from '../enums/ApiEndpoints'; import { ApiEndpoints } from '../enums/ApiEndpoints';
import { ModelType } from '../enums/ModelType'; import { ModelType } from '../enums/ModelType';
import { InvenTreeIcon } from '../functions/icons'; import { InvenTreeIcon } from '../functions/icons';
@ -291,10 +289,14 @@ function LineItemFormRow({
order: record?.order order: record?.order
}); });
// Generate new serial numbers // Generate new serial numbers
serialNumberGenerator.update({ if (trackable) {
part: record?.supplier_part_detail?.part, serialNumberGenerator.update({
quantity: props.item.quantity part: record?.supplier_part_detail?.part,
}); quantity: props.item.quantity
});
} else {
props.changeFn(props.idx, 'serial_numbers', undefined);
}
} }
}); });
@ -564,7 +566,10 @@ function LineItemFormRow({
)} )}
<TableFieldExtraRow <TableFieldExtraRow
visible={batchOpen} visible={batchOpen}
onValueChange={(value) => props.changeFn(props.idx, 'batch', value)} onValueChange={(value) => {
props.changeFn(props.idx, 'batch_code', value);
}}
fieldName='batch_code'
fieldDefinition={{ fieldDefinition={{
field_type: 'string', field_type: 'string',
label: t`Batch Code`, label: t`Batch Code`,
@ -578,6 +583,7 @@ function LineItemFormRow({
onValueChange={(value) => onValueChange={(value) =>
props.changeFn(props.idx, 'serial_numbers', value) props.changeFn(props.idx, 'serial_numbers', value)
} }
fieldName='serial_numbers'
fieldDefinition={{ fieldDefinition={{
field_type: 'string', field_type: 'string',
label: t`Serial Numbers`, label: t`Serial Numbers`,
@ -589,6 +595,7 @@ function LineItemFormRow({
<TableFieldExtraRow <TableFieldExtraRow
visible={packagingOpen} visible={packagingOpen}
onValueChange={(value) => props.changeFn(props.idx, 'packaging', value)} onValueChange={(value) => props.changeFn(props.idx, 'packaging', value)}
fieldName='packaging'
fieldDefinition={{ fieldDefinition={{
field_type: 'string', field_type: 'string',
label: t`Packaging` label: t`Packaging`
@ -599,6 +606,7 @@ function LineItemFormRow({
<TableFieldExtraRow <TableFieldExtraRow
visible={statusOpen} visible={statusOpen}
defaultValue={10} defaultValue={10}
fieldName='status'
onValueChange={(value) => props.changeFn(props.idx, 'status', value)} onValueChange={(value) => props.changeFn(props.idx, 'status', value)}
fieldDefinition={{ fieldDefinition={{
field_type: 'choice', field_type: 'choice',
@ -610,6 +618,7 @@ function LineItemFormRow({
/> />
<TableFieldExtraRow <TableFieldExtraRow
visible={noteOpen} visible={noteOpen}
fieldName='note'
onValueChange={(value) => props.changeFn(props.idx, 'note', value)} onValueChange={(value) => props.changeFn(props.idx, 'note', value)}
fieldDefinition={{ fieldDefinition={{
field_type: 'string', field_type: 'string',
@ -634,23 +643,10 @@ type LineItemsForm = {
}; };
export function useReceiveLineItems(props: LineItemsForm) { export function useReceiveLineItems(props: LineItemsForm) {
const { data } = useQuery({ const stockStatusCodes = useMemo(
queryKey: ['stock', 'status'], () => getStatusCodeOptions(ModelType.stockitem),
queryFn: async () => { []
return api.get(apiUrl(ApiEndpoints.stock_status)).then((response) => { );
if (response.status === 200) {
const entries = Object.values(response.data.values);
const mapped = entries.map((item: any) => {
return {
value: item.key,
display_name: item.label
};
});
return mapped;
}
});
}
});
const records = Object.fromEntries( const records = Object.fromEntries(
props.items.map((item) => [item.pk, item]) props.items.map((item) => [item.pk, item])
@ -660,44 +656,46 @@ export function useReceiveLineItems(props: LineItemsForm) {
(elem) => elem.quantity !== elem.received (elem) => elem.quantity !== elem.received
); );
const fields: ApiFormFieldSet = { const fields: ApiFormFieldSet = useMemo(() => {
id: { return {
value: props.orderPk, id: {
hidden: true value: props.orderPk,
}, hidden: true
items: {
field_type: 'table',
value: filteredItems.map((elem, idx) => {
return {
line_item: elem.pk,
location: elem.destination ?? elem.destination_detail?.pk ?? null,
quantity: elem.quantity - elem.received,
batch_code: '',
serial_numbers: '',
status: 10,
barcode: null
};
}),
modelRenderer: (row: TableFieldRowProps) => {
const record = records[row.item.line_item];
return (
<LineItemFormRow
props={row}
record={record}
statuses={data}
key={record.pk}
/>
);
}, },
headers: [t`Part`, t`SKU`, t`Received`, t`Quantity`, t`Actions`] items: {
}, field_type: 'table',
location: { value: filteredItems.map((elem, idx) => {
filters: { return {
structural: false line_item: elem.pk,
location: elem.destination ?? elem.destination_detail?.pk ?? null,
quantity: elem.quantity - elem.received,
batch_code: '',
serial_numbers: '',
status: 10,
barcode: null
};
}),
modelRenderer: (row: TableFieldRowProps) => {
const record = records[row.item.line_item];
return (
<LineItemFormRow
props={row}
record={record}
statuses={stockStatusCodes}
key={record.pk}
/>
);
},
headers: [t`Part`, t`SKU`, t`Received`, t`Quantity`, t`Actions`]
},
location: {
filters: {
structural: false
}
} }
} };
}; }, [filteredItems, props, stockStatusCodes]);
return useCreateApiFormModal({ return useCreateApiFormModal({
...props.formProps, ...props.formProps,
@ -707,6 +705,7 @@ export function useReceiveLineItems(props: LineItemsForm) {
initialData: { initialData: {
location: props.destinationPk location: props.destinationPk
}, },
size: '80%' size: '80%',
successMessage: t`Items received`
}); });
} }