2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-11-13 19:36:46 +00:00

[UI] Order form improvements (#10802)

* Auto-fill supplier parts in order wizard

* Copy supplier part SKU from order parts wizard

* Add "on_order" filter to BuildLine table

* Allow ordering by production and ordering quantities

* Allow specification of purchase price

* Bump API version

* Adjust UI testings
This commit is contained in:
Oliver
2025-11-11 17:29:18 +11:00
committed by GitHub
parent f3c1cc12af
commit d829d3a548
7 changed files with 54 additions and 7 deletions

View File

@@ -1,12 +1,16 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 424 INVENTREE_API_VERSION = 425
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ INVENTREE_API_TEXT = """
v424 -> 2025-11-05 : https://github.com/inventree/InvenTree/pull/10730 v425 -> 2025-11-11 : https://github.com/inventree/InvenTree/pull/10802
- Adds "on_order" filter to the BuildLine API endpoint
- Allow BuildLine list to be ordered by "on_order" and "in_production" fields
v424 -> 2025-11-11 : https://github.com/inventree/InvenTree/pull/10730
- Adds more lower / upper bounds to integer fields in the API schema due to bump to Django 5.2- no functional changes - Adds more lower / upper bounds to integer fields in the API schema due to bump to Django 5.2- no functional changes
v423 -> 2025-11-05 : https://github.com/inventree/InvenTree/pull/10772 v423 -> 2025-11-05 : https://github.com/inventree/InvenTree/pull/10772

View File

@@ -520,6 +520,15 @@ class BuildLineFilter(FilterSet):
return queryset.filter(flt) return queryset.filter(flt)
return queryset.exclude(flt) return queryset.exclude(flt)
on_order = rest_filters.BooleanFilter(label=_('On Order'), method='filter_on_order')
def filter_on_order(self, queryset, name, value):
"""Filter by whether there is stock on order for each BuildLine."""
if str2bool(value):
return queryset.filter(on_order__gt=0)
else:
return queryset.filter(on_order=0)
class BuildLineMixin(SerializerContextMixin): class BuildLineMixin(SerializerContextMixin):
"""Mixin class for BuildLine API endpoints.""" """Mixin class for BuildLine API endpoints."""
@@ -606,6 +615,8 @@ class BuildLineList(
'trackable', 'trackable',
'allow_variants', 'allow_variants',
'inherited', 'inherited',
'on_order',
'scheduled_to_build',
] ]
ordering_field_aliases = { ordering_field_aliases = {

View File

@@ -3,6 +3,7 @@ import {
ActionIcon, ActionIcon,
Button, Button,
type DefaultMantineColor, type DefaultMantineColor,
type FloatingPosition,
CopyButton as MantineCopyButton, CopyButton as MantineCopyButton,
type MantineSize, type MantineSize,
Text, Text,
@@ -16,12 +17,18 @@ import type { JSX } from 'react';
export function CopyButton({ export function CopyButton({
value, value,
label, label,
tooltip,
disabled,
tooltipPosition,
content, content,
size, size,
color = 'gray' color = 'gray'
}: Readonly<{ }: Readonly<{
value: any; value: any;
label?: string; label?: string;
tooltip?: string;
disabled?: boolean;
tooltipPosition?: FloatingPosition;
content?: JSX.Element; content?: JSX.Element;
size?: MantineSize; size?: MantineSize;
color?: DefaultMantineColor; color?: DefaultMantineColor;
@@ -31,8 +38,13 @@ export function CopyButton({
return ( return (
<MantineCopyButton value={value}> <MantineCopyButton value={value}>
{({ copied, copy }) => ( {({ copied, copy }) => (
<Tooltip label={copied ? t`Copied` : t`Copy`} withArrow> <Tooltip
label={copied ? t`Copied` : (tooltip ?? t`Copy`)}
withArrow
position={tooltipPosition}
>
<ButtonComponent <ButtonComponent
disabled={disabled}
color={copied ? 'teal' : color} color={copied ? 'teal' : color}
onClick={copy} onClick={copy}
variant='transparent' variant='transparent'

View File

@@ -32,6 +32,7 @@ import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance'; import { useInstance } from '../../hooks/UseInstance';
import useWizard from '../../hooks/UseWizard'; import useWizard from '../../hooks/UseWizard';
import { RenderPartColumn } from '../../tables/ColumnRenderers'; import { RenderPartColumn } from '../../tables/ColumnRenderers';
import { CopyButton } from '../buttons/CopyButton';
import RemoveRowButton from '../buttons/RemoveRowButton'; import RemoveRowButton from '../buttons/RemoveRowButton';
import { StandaloneField } from '../forms/StandaloneField'; import { StandaloneField } from '../forms/StandaloneField';
import Expand from '../items/Expand'; import Expand from '../items/Expand';
@@ -235,6 +236,8 @@ function SelectPartsStep({
quantity: { quantity: {
// TODO: Auto-fill with the desired quantity // TODO: Auto-fill with the desired quantity
}, },
purchase_price: {},
purchase_price_currency: {},
merge_items: {} merge_items: {}
}; };
}, [selectedRecord]); }, [selectedRecord]);
@@ -299,6 +302,7 @@ function SelectPartsStep({
model: ModelType.supplierpart, model: ModelType.supplierpart,
placeholder: t`Select supplier part`, placeholder: t`Select supplier part`,
required: true, required: true,
autoFill: true,
value: record.supplier_part?.pk, value: record.supplier_part?.pk,
onValueChange: (value, instance) => { onValueChange: (value, instance) => {
onSelectSupplierPart(record.part.pk, instance); onSelectSupplierPart(record.part.pk, instance);
@@ -312,6 +316,12 @@ function SelectPartsStep({
}} }}
/> />
</Expand> </Expand>
<CopyButton
disabled={!record.supplier_part?.pk}
value={record.supplier_part?.SKU}
tooltipPosition='top-end'
tooltip={t`Copy supplier part number`}
/>
<AddItemButton <AddItemButton
tooltip={t`New supplier part`} tooltip={t`New supplier part`}
tooltipAlignment='top-end' tooltipAlignment='top-end'

View File

@@ -107,8 +107,8 @@ export function usePurchaseOrderLineItemFields({
}; };
} }
}, },
quantity: {},
reference: {}, reference: {},
quantity: {},
purchase_price: { purchase_price: {
icon: <IconCurrencyDollar />, icon: <IconCurrencyDollar />,
value: purchasePrice, value: purchasePrice,

View File

@@ -190,7 +190,7 @@ export default function BuildLineTable({
{ {
name: 'available', name: 'available',
label: t`Available`, label: t`Available`,
description: t`Show items with available stock` description: t`Show items with sufficient available stock`
}, },
{ {
name: 'consumable', name: 'consumable',
@@ -217,6 +217,11 @@ export default function BuildLineTable({
label: t`Tracked`, label: t`Tracked`,
description: t`Show tracked lines` description: t`Show tracked lines`
}, },
{
name: 'on_order',
label: t`On Order`,
description: t`Show items with stock on order`
},
PartCategoryFilter() PartCategoryFilter()
]; ];
}, []); }, []);
@@ -455,6 +460,8 @@ export default function BuildLineTable({
}, },
{ {
accessor: 'in_production', accessor: 'in_production',
sortable: true,
ordering: 'scheduled_to_build',
render: (record: any) => { render: (record: any) => {
if (record.scheduled_to_build > 0) { if (record.scheduled_to_build > 0) {
return ( return (
@@ -471,7 +478,8 @@ export default function BuildLineTable({
}, },
DecimalColumn({ DecimalColumn({
accessor: 'on_order', accessor: 'on_order',
defaultVisible: false defaultVisible: false,
sortable: true
}), }),
{ {
accessor: 'allocated', accessor: 'allocated',

View File

@@ -263,7 +263,9 @@ test('Purchase Orders - Order Parts', async ({ browser }) => {
// Select supplier part // Select supplier part
await page.getByLabel('related-field-supplier_part').click(); await page.getByLabel('related-field-supplier_part').click();
await page.getByText('WM1731-ND').click(); await page
.getByRole('option', { name: 'Thumbnail DigiKey WM1731-ND' })
.click();
// Option to create a new supplier part // Option to create a new supplier part
await page.getByLabel('action-button-new-supplier-part').click(); await page.getByLabel('action-button-new-supplier-part').click();