2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 03:26:45 +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 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
*/

View File

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