mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
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:
parent
eec53ffd82
commit
2cf959cb8d
@ -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>
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
@ -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%'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user