Forms refactor (#7981)

* Refactor "receive stock" table

- Display errors
- Fix infinite rendering loop
- Correctly set values to undefined on close

* Refactor stock operations table

* Fix for "change stock status" form

* Fix default values

* Unit test fix
This commit is contained in:
Oliver 2024-08-25 11:04:18 +10:00 committed by GitHub
parent eec53ffd82
commit 2cf959cb8d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 157 additions and 146 deletions

View File

@ -34,6 +34,7 @@ export function TableField({
const onRowFieldChange = (idx: number, key: string, value: any) => { const onRowFieldChange = (idx: number, key: string, value: any) => {
const val = field.value; const val = field.value;
val[idx][key] = value; val[idx][key] = value;
field.onChange(val); field.onChange(val);
}; };
@ -114,11 +115,13 @@ export function TableFieldExtraRow({
fieldDefinition, fieldDefinition,
defaultValue, defaultValue,
emptyValue, emptyValue,
error,
onValueChange onValueChange
}: { }: {
visible: boolean; visible: boolean;
fieldDefinition: ApiFormFieldType; fieldDefinition: ApiFormFieldType;
defaultValue?: any; defaultValue?: any;
error?: string;
emptyValue?: any; emptyValue?: any;
onValueChange: (value: any) => void; onValueChange: (value: any) => void;
}) { }) {
@ -151,6 +154,7 @@ export function TableFieldExtraRow({
<StandaloneField <StandaloneField
fieldDefinition={field} fieldDefinition={field}
defaultValue={defaultValue} defaultValue={defaultValue}
error={error}
/> />
</Group> </Group>
</Table.Td> </Table.Td>

View File

@ -554,12 +554,14 @@ function BuildAllocateLineRow({
</Table.Td> </Table.Td>
<Table.Td> <Table.Td>
<StandaloneField <StandaloneField
fieldName="stock_item"
fieldDefinition={stockField} fieldDefinition={stockField}
error={props.rowErrors?.stock_item?.message} error={props.rowErrors?.stock_item?.message}
/> />
</Table.Td> </Table.Td>
<Table.Td> <Table.Td>
<StandaloneField <StandaloneField
fieldName="quantity"
fieldDefinition={quantityField} fieldDefinition={quantityField}
error={props.rowErrors?.quantity?.message} error={props.rowErrors?.quantity?.message}
/> />

View File

@ -5,7 +5,6 @@ import {
FocusTrap, FocusTrap,
Group, Group,
Modal, Modal,
NumberInput,
Table, Table,
TextInput TextInput
} from '@mantine/core'; } from '@mantine/core';
@ -34,7 +33,10 @@ import {
ApiFormAdjustFilterType, ApiFormAdjustFilterType,
ApiFormFieldSet ApiFormFieldSet
} from '../components/forms/fields/ApiFormField'; } from '../components/forms/fields/ApiFormField';
import { TableFieldExtraRow } from '../components/forms/fields/TableField'; import {
TableFieldExtraRow,
TableFieldRowProps
} from '../components/forms/fields/TableField';
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';
@ -192,67 +194,53 @@ export function usePurchaseOrderFields(): ApiFormFieldSet {
* Render a table row for a single TableField entry * Render a table row for a single TableField entry
*/ */
function LineItemFormRow({ function LineItemFormRow({
input, props,
record, record,
statuses statuses
}: { }: {
input: any; props: TableFieldRowProps;
record: any; record: any;
statuses: any; statuses: any;
}) { }) {
// Barcode Modal state // Barcode Modal state
const [opened, { open, close }] = useDisclosure(false); const [opened, { open, close }] = useDisclosure(false, {
onClose: () => props.changeFn(props.idx, 'barcode', undefined)
});
// Location value const [locationOpen, locationHandlers] = useDisclosure(false, {
const [location, setLocation] = useState( onClose: () => props.changeFn(props.idx, 'location', undefined)
input.item.location ?? });
record.part_detail.default_location ??
record.part_detail.category_default_location
);
const [locationOpen, locationHandlers] = useDisclosure(
location ? true : false,
{
onClose: () => input.changeFn(input.idx, 'location', null),
onOpen: () => input.changeFn(input.idx, 'location', location)
}
);
// Change form value when state is altered
useEffect(() => {
input.changeFn(input.idx, 'location', location);
}, [location]);
// Batch code generator
const batchCodeGenerator = useBatchCodeGenerator((value: any) => { const batchCodeGenerator = useBatchCodeGenerator((value: any) => {
if (!batchCode) { if (value) {
setBatchCode(value); props.changeFn(props.idx, 'batch_code', value);
} }
}); });
// Serial numbebr generator
const serialNumberGenerator = useSerialNumberGenerator((value: any) => { const serialNumberGenerator = useSerialNumberGenerator((value: any) => {
if (!serials) { if (value) {
setSerials(value); props.changeFn(props.idx, 'serial_numbers', value);
} }
}); });
const [packagingOpen, packagingHandlers] = useDisclosure(false, { const [packagingOpen, packagingHandlers] = useDisclosure(false, {
onClose: () => { onClose: () => {
input.changeFn(input.idx, 'packaging', undefined); props.changeFn(props.idx, 'packaging', undefined);
} }
}); });
const [noteOpen, noteHandlers] = useDisclosure(false, { const [noteOpen, noteHandlers] = useDisclosure(false, {
onClose: () => { onClose: () => {
input.changeFn(input.idx, 'note', undefined); props.changeFn(props.idx, 'note', undefined);
} }
}); });
// State for serializing
const [batchCode, setBatchCode] = useState<string>('');
const [serials, setSerials] = useState<string>('');
const [batchOpen, batchHandlers] = useDisclosure(false, { const [batchOpen, batchHandlers] = useDisclosure(false, {
onClose: () => { onClose: () => {
input.changeFn(input.idx, 'batch_code', undefined); props.changeFn(props.idx, 'batch_code', undefined);
input.changeFn(input.idx, 'serial_numbers', ''); props.changeFn(props.idx, 'serial_numbers', undefined);
}, },
onOpen: () => { onOpen: () => {
// Generate a new batch code // Generate a new batch code
@ -263,23 +251,23 @@ function LineItemFormRow({
// Generate new serial numbers // Generate new serial numbers
serialNumberGenerator.update({ serialNumberGenerator.update({
part: record?.supplier_part_detail?.part, part: record?.supplier_part_detail?.part,
quantity: input.item.quantity quantity: props.item.quantity
}); });
} }
}); });
// Status value // Status value
const [statusOpen, statusHandlers] = useDisclosure(false, { const [statusOpen, statusHandlers] = useDisclosure(false, {
onClose: () => input.changeFn(input.idx, 'status', 10) onClose: () => props.changeFn(props.idx, 'status', undefined)
}); });
// Barcode value // Barcode value
const [barcodeInput, setBarcodeInput] = useState<any>(''); const [barcodeInput, setBarcodeInput] = useState<any>('');
const [barcode, setBarcode] = useState(null); const [barcode, setBarcode] = useState<String | undefined>(undefined);
// Change form value when state is altered // Change form value when state is altered
useEffect(() => { useEffect(() => {
input.changeFn(input.idx, 'barcode', barcode); props.changeFn(props.idx, 'barcode', barcode);
}, [barcode]); }, [barcode]);
// Update location field description on state change // Update location field description on state change
@ -371,13 +359,16 @@ function LineItemFormRow({
progressLabel progressLabel
/> />
</Table.Td> </Table.Td>
<Table.Td style={{ width: '1%', whiteSpace: 'nowrap' }}> <Table.Td style={{ whiteSpace: 'nowrap' }}>
<NumberInput <StandaloneField
value={input.item.quantity} fieldName="quantity"
style={{ width: '100px' }} fieldDefinition={{
max={input.item.quantity} field_type: 'number',
min={0} value: props.item.quantity,
onChange={(value) => input.changeFn(input.idx, 'quantity', value)} onValueChange: (value) =>
props.changeFn(props.idx, 'quantity', value)
}}
error={props.rowErrors?.quantity?.message}
/> />
</Table.Td> </Table.Td>
<Table.Td style={{ width: '1%', whiteSpace: 'nowrap' }}> <Table.Td style={{ width: '1%', whiteSpace: 'nowrap' }}>
@ -404,6 +395,7 @@ function LineItemFormRow({
size="sm" size="sm"
icon={<InvenTreeIcon icon="packaging" />} icon={<InvenTreeIcon icon="packaging" />}
tooltip={t`Adjust Packaging`} tooltip={t`Adjust Packaging`}
tooltipAlignment="top"
onClick={() => packagingHandlers.toggle()} onClick={() => packagingHandlers.toggle()}
variant={packagingOpen ? 'filled' : 'transparent'} variant={packagingOpen ? 'filled' : 'transparent'}
/> />
@ -428,7 +420,7 @@ function LineItemFormRow({
tooltipAlignment="top" tooltipAlignment="top"
variant="filled" variant="filled"
color="red" color="red"
onClick={() => setBarcode(null)} onClick={() => setBarcode(undefined)}
/> />
) : ( ) : (
<ActionButton <ActionButton
@ -439,7 +431,7 @@ function LineItemFormRow({
onClick={() => open()} onClick={() => open()}
/> />
)} )}
<RemoveRowButton onClick={() => input.removeFn(input.idx)} /> <RemoveRowButton onClick={() => props.removeFn(props.idx)} />
</Flex> </Flex>
</Table.Td> </Table.Td>
</Table.Tr> </Table.Tr>
@ -459,7 +451,7 @@ function LineItemFormRow({
structural: false structural: false
}, },
onValueChange: (value) => { onValueChange: (value) => {
setLocation(value); props.changeFn(props.idx, 'location', value);
}, },
description: locationDescription, description: locationDescription,
value: location, value: location,
@ -480,7 +472,9 @@ function LineItemFormRow({
icon={<InvenTreeIcon icon="default_location" />} icon={<InvenTreeIcon icon="default_location" />}
tooltip={t`Store at default location`} tooltip={t`Store at default location`}
onClick={() => onClick={() =>
setLocation( props.changeFn(
props.idx,
'location',
record.part_detail.default_location ?? record.part_detail.default_location ??
record.part_detail.category_default_location record.part_detail.category_default_location
) )
@ -492,7 +486,9 @@ function LineItemFormRow({
<ActionButton <ActionButton
icon={<InvenTreeIcon icon="destination" />} icon={<InvenTreeIcon icon="destination" />}
tooltip={t`Store at line item destination `} tooltip={t`Store at line item destination `}
onClick={() => setLocation(record.destination)} onClick={() =>
props.changeFn(props.idx, 'location', record.destination)
}
tooltipAlignment="top" tooltipAlignment="top"
/> />
)} )}
@ -502,7 +498,13 @@ function LineItemFormRow({
<ActionButton <ActionButton
icon={<InvenTreeIcon icon="repeat_destination" />} icon={<InvenTreeIcon icon="repeat_destination" />}
tooltip={t`Store with already received stock`} tooltip={t`Store with already received stock`}
onClick={() => setLocation(record.destination_detail.pk)} onClick={() =>
props.changeFn(
props.idx,
'location',
record.destination_detail.pk
)
}
tooltipAlignment="top" tooltipAlignment="top"
/> />
)} )}
@ -513,51 +515,56 @@ function LineItemFormRow({
)} )}
<TableFieldExtraRow <TableFieldExtraRow
visible={batchOpen} visible={batchOpen}
onValueChange={(value) => input.changeFn(input.idx, 'batch', value)} onValueChange={(value) => props.changeFn(props.idx, 'batch', value)}
fieldDefinition={{ fieldDefinition={{
field_type: 'string', field_type: 'string',
label: t`Batch Code`, label: t`Batch Code`,
value: batchCode value: props.item.batch_code
}} }}
error={props.rowErrors?.batch_code?.message}
/> />
<TableFieldExtraRow <TableFieldExtraRow
visible={batchOpen && record.trackable} visible={batchOpen && record.trackable}
onValueChange={(value) => onValueChange={(value) =>
input.changeFn(input.idx, 'serial_numbers', value) props.changeFn(props.idx, 'serial_numbers', value)
} }
fieldDefinition={{ fieldDefinition={{
field_type: 'string', field_type: 'string',
label: t`Serial numbers`, label: t`Serial numbers`,
value: serials value: props.item.serial_numbers
}} }}
error={props.rowErrors?.serial_numbers?.message}
/> />
<TableFieldExtraRow <TableFieldExtraRow
visible={packagingOpen} visible={packagingOpen}
onValueChange={(value) => input.changeFn(input.idx, 'packaging', value)} onValueChange={(value) => props.changeFn(props.idx, 'packaging', value)}
fieldDefinition={{ fieldDefinition={{
field_type: 'string', field_type: 'string',
label: t`Packaging` label: t`Packaging`
}} }}
defaultValue={record?.supplier_part_detail?.packaging} defaultValue={record?.supplier_part_detail?.packaging}
error={props.rowErrors?.packaging?.message}
/> />
<TableFieldExtraRow <TableFieldExtraRow
visible={statusOpen} visible={statusOpen}
defaultValue={10} defaultValue={10}
onValueChange={(value) => input.changeFn(input.idx, 'status', value)} onValueChange={(value) => props.changeFn(props.idx, 'status', value)}
fieldDefinition={{ fieldDefinition={{
field_type: 'choice', field_type: 'choice',
api_url: apiUrl(ApiEndpoints.stock_status), api_url: apiUrl(ApiEndpoints.stock_status),
choices: statuses, choices: statuses,
label: t`Status` label: t`Status`
}} }}
error={props.rowErrors?.status?.message}
/> />
<TableFieldExtraRow <TableFieldExtraRow
visible={noteOpen} visible={noteOpen}
onValueChange={(value) => input.changeFn(input.idx, 'note', value)} onValueChange={(value) => props.changeFn(props.idx, 'note', value)}
fieldDefinition={{ fieldDefinition={{
field_type: 'string', field_type: 'string',
label: t`Note` label: t`Note`
}} }}
error={props.rowErrors?.note?.message}
/> />
</> </>
); );
@ -619,12 +626,12 @@ export function useReceiveLineItems(props: LineItemsForm) {
barcode: null barcode: null
}; };
}), }),
modelRenderer: (instance) => { modelRenderer: (row: TableFieldRowProps) => {
const record = records[instance.item.line_item]; const record = records[row.item.line_item];
return ( return (
<LineItemFormRow <LineItemFormRow
input={instance} props={row}
record={record} record={record}
statuses={data} statuses={data}
key={record.pk} key={record.pk}
@ -640,18 +647,14 @@ export function useReceiveLineItems(props: LineItemsForm) {
} }
}; };
const url = apiUrl(ApiEndpoints.purchase_order_receive, null, {
id: props.orderPk
});
return useCreateApiFormModal({ return useCreateApiFormModal({
...props.formProps, ...props.formProps,
url: url, url: apiUrl(ApiEndpoints.purchase_order_receive, props.orderPk),
title: t`Receive Line Items`, title: t`Receive Line Items`,
fields: fields, fields: fields,
initialData: { initialData: {
location: null location: null
}, },
size: 'xl' size: '80%'
}); });
} }

View File

@ -1,18 +1,22 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Flex, Group, NumberInput, Skeleton, Table, Text } from '@mantine/core'; import { Flex, Group, Skeleton, Table, Text } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { modals } from '@mantine/modals'; import { modals } from '@mantine/modals';
import { useQuery, useSuspenseQuery } from '@tanstack/react-query'; import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { Suspense, useCallback, useMemo, useState } from 'react'; import { Suspense, useMemo, useState } from 'react';
import { api } from '../App'; 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 { import {
ApiFormAdjustFilterType, ApiFormAdjustFilterType,
ApiFormFieldSet ApiFormFieldSet
} from '../components/forms/fields/ApiFormField'; } from '../components/forms/fields/ApiFormField';
import { TableFieldExtraRow } from '../components/forms/fields/TableField'; import {
TableFieldExtraRow,
TableFieldRowProps
} from '../components/forms/fields/TableField';
import { Thumbnail } from '../components/images/Thumbnail'; import { Thumbnail } from '../components/images/Thumbnail';
import { StylishText } from '../components/items/StylishText'; import { StylishText } from '../components/items/StylishText';
import { StatusRenderer } from '../components/render/StatusRenderer'; import { StatusRenderer } from '../components/render/StatusRenderer';
@ -139,7 +143,9 @@ export function useStockFields({
value: batchCode, value: batchCode,
onValueChange: (value) => setBatchCode(value) onValueChange: (value) => setBatchCode(value)
}, },
status_custom_key: {}, status_custom_key: {
label: t`Stock Status`
},
expiry_date: { expiry_date: {
// TODO: icon // TODO: icon
}, },
@ -295,47 +301,37 @@ type StockRow = {
}; };
function StockOperationsRow({ function StockOperationsRow({
input, props,
transfer = false, transfer = false,
add = false, add = false,
setMax = false, setMax = false,
merge = false, merge = false,
record record
}: { }: {
input: StockRow; props: TableFieldRowProps;
transfer?: boolean; transfer?: boolean;
add?: boolean; add?: boolean;
setMax?: boolean; setMax?: boolean;
merge?: boolean; merge?: boolean;
record?: any; record?: any;
}) { }) {
const item = input.item; const [quantity, setQuantity] = useState<StockItemQuantity>(
add ? 0 : props.item?.quantity ?? 0
const [value, setValue] = useState<StockItemQuantity>(
add ? 0 : item.quantity ?? 0
);
const onChange = useCallback(
(value: any) => {
setValue(value);
input.changeFn(input.idx, 'quantity', value);
},
[item]
); );
const removeAndRefresh = () => { const removeAndRefresh = () => {
input.removeFn(input.idx); props.removeFn(props.idx);
}; };
const [packagingOpen, packagingHandlers] = useDisclosure(false, { const [packagingOpen, packagingHandlers] = useDisclosure(false, {
onOpen: () => { onOpen: () => {
if (transfer) { if (transfer) {
input.changeFn(input.idx, 'packaging', record?.packaging || undefined); props.changeFn(props.idx, 'packaging', record?.packaging || undefined);
} }
}, },
onClose: () => { onClose: () => {
if (transfer) { if (transfer) {
input.changeFn(input.idx, 'packaging', undefined); props.changeFn(props.idx, 'packaging', undefined);
} }
} }
}); });
@ -371,25 +367,24 @@ function StockOperationsRow({
{record.location ? record.location_detail?.pathstring : '-'} {record.location ? record.location_detail?.pathstring : '-'}
</Table.Td> </Table.Td>
<Table.Td> <Table.Td>
<Flex align="center" gap="xs"> <Group grow justify="space-between" wrap="nowrap">
<Group justify="space-between">
<Text>{stockString}</Text> <Text>{stockString}</Text>
<StatusRenderer <StatusRenderer status={record.status} type={ModelType.stockitem} />
status={record.status}
type={ModelType.stockitem}
/>
</Group> </Group>
</Flex>
</Table.Td> </Table.Td>
{!merge && ( {!merge && (
<Table.Td> <Table.Td>
<NumberInput <StandaloneField
value={value} fieldName="quantity"
onChange={onChange} fieldDefinition={{
disabled={!!record.serial && record.quantity == 1} field_type: 'number',
max={setMax ? record.quantity : undefined} value: quantity,
min={0} onValueChange: (value: any) => {
style={{ maxWidth: '100px' }} setQuantity(value);
props.changeFn(props.idx, 'quantity', value);
}
}}
error={props.rowErrors?.quantity?.message}
/> />
</Table.Td> </Table.Td>
)} )}
@ -397,7 +392,9 @@ function StockOperationsRow({
<Flex gap="3px"> <Flex gap="3px">
{transfer && ( {transfer && (
<ActionButton <ActionButton
onClick={() => moveToDefault(record, value, removeAndRefresh)} onClick={() =>
moveToDefault(record, props.item.quantity, removeAndRefresh)
}
icon={<InvenTreeIcon icon="default_location" />} icon={<InvenTreeIcon icon="default_location" />}
tooltip={t`Move to default location`} tooltip={t`Move to default location`}
tooltipAlignment="top" tooltipAlignment="top"
@ -416,7 +413,7 @@ function StockOperationsRow({
variant={packagingOpen ? 'filled' : 'transparent'} variant={packagingOpen ? 'filled' : 'transparent'}
/> />
)} )}
<RemoveRowButton onClick={() => input.removeFn(input.idx)} /> <RemoveRowButton onClick={() => props.removeFn(props.idx)} />
</Flex> </Flex>
</Table.Td> </Table.Td>
</Table.Tr> </Table.Tr>
@ -424,7 +421,7 @@ function StockOperationsRow({
<TableFieldExtraRow <TableFieldExtraRow
visible={transfer && packagingOpen} visible={transfer && packagingOpen}
onValueChange={(value: any) => { onValueChange={(value: any) => {
input.changeFn(input.idx, 'packaging', value || undefined); props.changeFn(props.idx, 'packaging', value || undefined);
}} }}
fieldDefinition={{ fieldDefinition={{
field_type: 'string', field_type: 'string',
@ -452,9 +449,9 @@ function mapAdjustmentItems(items: any[]) {
return { return {
pk: elem.pk, pk: elem.pk,
quantity: elem.quantity, quantity: elem.quantity,
batch: elem.batch, batch: elem.batch || undefined,
status: elem.status, status: elem.status || undefined,
packaging: elem.packaging, packaging: elem.packaging || undefined,
obj: elem obj: elem
}; };
}); });
@ -473,14 +470,16 @@ function stockTransferFields(items: any[]): ApiFormFieldSet {
items: { items: {
field_type: 'table', field_type: 'table',
value: mapAdjustmentItems(items), value: mapAdjustmentItems(items),
modelRenderer: (val) => { modelRenderer: (row: TableFieldRowProps) => {
const record = records[row.item.pk];
return ( return (
<StockOperationsRow <StockOperationsRow
input={val} props={row}
transfer transfer
setMax setMax
key={val.item.pk} key={record.pk}
record={records[val.item.pk]} record={record}
/> />
); );
}, },
@ -508,13 +507,16 @@ function stockRemoveFields(items: any[]): ApiFormFieldSet {
items: { items: {
field_type: 'table', field_type: 'table',
value: mapAdjustmentItems(items), value: mapAdjustmentItems(items),
modelRenderer: (val) => { modelRenderer: (row: TableFieldRowProps) => {
const record = records[row.item.pk];
return ( return (
<StockOperationsRow <StockOperationsRow
input={val} props={row}
setMax setMax
key={val.item.pk} add
record={records[val.item.pk]} key={record.pk}
record={record}
/> />
); );
}, },
@ -537,14 +539,11 @@ function stockAddFields(items: any[]): ApiFormFieldSet {
items: { items: {
field_type: 'table', field_type: 'table',
value: mapAdjustmentItems(items), value: mapAdjustmentItems(items),
modelRenderer: (val) => { modelRenderer: (row: TableFieldRowProps) => {
const record = records[row.item.pk];
return ( return (
<StockOperationsRow <StockOperationsRow props={row} add key={record.pk} record={record} />
input={val}
add
key={val.item.pk}
record={records[val.item.pk]}
/>
); );
}, },
headers: [t`Part`, t`Location`, t`In Stock`, t`Add`, t`Actions`] headers: [t`Part`, t`Location`, t`In Stock`, t`Add`, t`Actions`]
@ -566,12 +565,12 @@ function stockCountFields(items: any[]): ApiFormFieldSet {
items: { items: {
field_type: 'table', field_type: 'table',
value: mapAdjustmentItems(items), value: mapAdjustmentItems(items),
modelRenderer: (val) => { modelRenderer: (row: TableFieldRowProps) => {
return ( return (
<StockOperationsRow <StockOperationsRow
input={val} props={row}
key={val.item.pk} key={row.item.pk}
record={records[val.item.pk]} record={records[row.item.pk]}
/> />
); );
}, },
@ -596,19 +595,19 @@ function stockChangeStatusFields(items: any[]): ApiFormFieldSet {
value: items.map((elem) => { value: items.map((elem) => {
return elem.pk; return elem.pk;
}), }),
modelRenderer: (val) => { modelRenderer: (row: TableFieldRowProps) => {
return ( return (
<StockOperationsRow <StockOperationsRow
input={val} props={row}
key={val.item} key={row.item}
merge merge
record={records[val.item]} record={records[row.item]}
/> />
); );
}, },
headers: [t`Part`, t`Location`, t`In Stock`, t`Actions`] headers: [t`Part`, t`Location`, t`In Stock`, t`Actions`]
}, },
status_custom_key: {}, status: {},
note: {} note: {}
}; };
@ -631,13 +630,13 @@ function stockMergeFields(items: any[]): ApiFormFieldSet {
obj: elem obj: elem
}; };
}), }),
modelRenderer: (val) => { modelRenderer: (row: TableFieldRowProps) => {
return ( return (
<StockOperationsRow <StockOperationsRow
input={val} props={row}
key={val.item.item} key={row.item.item}
merge merge
record={records[val.item.item]} record={records[row.item.item]}
/> />
); );
}, },
@ -673,13 +672,13 @@ function stockAssignFields(items: any[]): ApiFormFieldSet {
obj: elem obj: elem
}; };
}), }),
modelRenderer: (val) => { modelRenderer: (row: TableFieldRowProps) => {
return ( return (
<StockOperationsRow <StockOperationsRow
input={val} props={row}
key={val.item.item} key={row.item.item}
merge merge
record={records[val.item.item]} record={records[row.item.item]}
/> />
); );
}, },
@ -709,13 +708,15 @@ function stockDeleteFields(items: any[]): ApiFormFieldSet {
value: items.map((elem) => { value: items.map((elem) => {
return elem.pk; return elem.pk;
}), }),
modelRenderer: (val) => { modelRenderer: (row: TableFieldRowProps) => {
const record = records[row.item];
return ( return (
<StockOperationsRow <StockOperationsRow
input={val} props={row}
key={val.item} key={record.pk}
merge merge
record={records[val.item]} record={record}
/> />
); );
}, },
@ -803,6 +804,7 @@ function stockOperationModal({
url: endpoint, url: endpoint,
fields: fields, fields: fields,
title: title, title: title,
size: '80%',
onFormSuccess: () => refresh() onFormSuccess: () => refresh()
}); });
} }