2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-30 12:45:42 +00:00

SalesOrderShipment address (#10650)

* Adds "shipment_address" attribute to the SalesOrderShipment model:

- Allows different addresses for each shipment
- Defaults to the order shipment address (if not specified)

* Add unit testing for field validation

* Update SalesOrderShipment serializer

* Edit shipment address in UI

* Render date on shipment page

* Improve address rendering

* Update docs

* Bump API version

* Update CHANGELOG.md

* Fix API version
This commit is contained in:
Oliver
2025-10-23 16:37:43 +11:00
committed by GitHub
parent 754b2f2d66
commit ec33c57e85
13 changed files with 252 additions and 39 deletions

View File

@@ -1,5 +1,7 @@
import type { ReactNode } from 'react';
import { Text } from '@mantine/core';
import { ModelType } from '@lib/enums/ModelType';
import { getDetailUrl } from '@lib/functions/Navigation';
import { type InstanceRenderInterface, RenderInlineModel } from './Instance';
@@ -22,9 +24,19 @@ export function RenderAddress({
.join(', ');
const primary: string = instance.title || text;
const secondary: string = instance.title ? text : '';
const secondary: string = !!instance.title ? text : '';
return <RenderInlineModel primary={primary} secondary={secondary} />;
const suffix: ReactNode = instance.primary ? (
<Text size='xs'>Primary Address</Text>
) : null;
return (
<RenderInlineModel
primary={primary}
secondary={secondary}
suffix={suffix}
/>
);
}
/**

View File

@@ -372,8 +372,10 @@ export function useSalesOrderAllocateSerialsFields({
}
export function useSalesOrderShipmentFields({
customerId,
pending
}: {
customerId: number;
pending?: boolean;
}): ApiFormFieldSet {
return useMemo(() => {
@@ -388,11 +390,18 @@ export function useSalesOrderShipmentFields({
delivery_date: {
hidden: pending ?? true
},
shipment_address: {
placeholder: t`Leave blank to use the order address`,
filters: {
company: customerId,
ordering: '-primary'
}
},
tracking_number: {},
invoice_number: {},
link: {}
};
}, [pending]);
}, [customerId, pending]);
}
export function useSalesOrderShipmentCompleteFields({

View File

@@ -1,5 +1,5 @@
import { t } from '@lingui/core/macro';
import { Accordion, Grid, Skeleton, Stack } from '@mantine/core';
import { Accordion, Grid, Skeleton, Stack, Text } from '@mantine/core';
import { IconInfoCircle, IconList } from '@tabler/icons-react';
import { type ReactNode, useMemo } from 'react';
import { useParams } from 'react-router-dom';
@@ -194,8 +194,12 @@ export default function ReturnOrderDetail() {
name: 'address',
label: t`Return Address`,
icon: 'address',
hidden: !order.address_detail,
value_formatter: () => <RenderAddress instance={order.address_detail} />
value_formatter: () =>
order.address_detail ? (
<RenderAddress instance={order.address_detail} />
) : (
<Text size='sm' c='red'>{t`Not specified`}</Text>
)
},
{
type: 'text',

View File

@@ -1,5 +1,5 @@
import { t } from '@lingui/core/macro';
import { Accordion, Grid, Skeleton, Stack } from '@mantine/core';
import { Accordion, Grid, Skeleton, Stack, Text } from '@mantine/core';
import {
IconBookmark,
IconInfoCircle,
@@ -187,8 +187,12 @@ export default function SalesOrderDetail() {
name: 'address',
label: t`Shipping Address`,
icon: 'address',
hidden: !order.address_detail,
value_formatter: () => <RenderAddress instance={order.address_detail} />
value_formatter: () =>
order.address_detail ? (
<RenderAddress instance={order.address_detail} />
) : (
<Text size='sm' c='red'>{t`Not specified`}</Text>
)
},
{
type: 'text',
@@ -391,7 +395,12 @@ export default function SalesOrderDetail() {
name: 'shipments',
label: t`Shipments`,
icon: <IconTruckDelivery />,
content: <SalesOrderShipmentTable orderId={order.pk} />
content: (
<SalesOrderShipmentTable
orderId={order.pk}
customerId={order.customer}
/>
)
},
{
name: 'allocations',

View File

@@ -1,5 +1,5 @@
import { t } from '@lingui/core/macro';
import { Grid, Skeleton, Stack } from '@mantine/core';
import { Grid, Skeleton, Stack, Text } from '@mantine/core';
import { IconBookmark, IconInfoCircle } from '@tabler/icons-react';
import { useMemo } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
@@ -30,6 +30,7 @@ import AttachmentPanel from '../../components/panels/AttachmentPanel';
import NotesPanel from '../../components/panels/NotesPanel';
import type { PanelType } from '../../components/panels/Panel';
import { PanelGroup } from '../../components/panels/PanelGroup';
import { RenderAddress } from '../../components/render/Company';
import { formatDate } from '../../defaults/formatters';
import {
useSalesOrderShipmentCompleteFields,
@@ -104,6 +105,18 @@ export default function SalesOrderShipmentDetail() {
model_field: 'name',
hidden: !data.customer
},
{
type: 'link',
external: true,
name: 'link',
label: t`Link`,
copy: true,
hidden: !shipment.link
}
];
// Top right: Shipment information
const tr: DetailsField[] = [
{
type: 'text',
name: 'customer_reference',
@@ -119,16 +132,6 @@ export default function SalesOrderShipmentDetail() {
label: t`Shipment Reference`,
copy: true
},
{
type: 'text',
name: 'allocated_items',
icon: 'packages',
label: t`Allocated Items`
}
];
// Top right: Shipment information
const tr: DetailsField[] = [
{
type: 'text',
name: 'tracking_number',
@@ -144,6 +147,38 @@ export default function SalesOrderShipmentDetail() {
icon: 'serial',
value_formatter: () => shipment.invoice_number || '---',
copy: !!shipment.invoice_number
}
];
const address: any =
shipment.shipment_address_detail || shipment.order_detail?.address_detail;
const bl: DetailsField[] = [
{
type: 'text',
name: 'address',
label: t`Shipping Address`,
icon: 'address',
value_formatter: () =>
address ? (
<RenderAddress
instance={
shipment.shipment_address_detail ||
shipment.order_detail?.address_detail
}
/>
) : (
<Text size='sm' c='red'>{t`Not specified`}</Text>
)
}
];
const br: DetailsField[] = [
{
type: 'text',
name: 'allocated_items',
icon: 'packages',
label: t`Allocated Items`
},
{
type: 'text',
@@ -160,14 +195,6 @@ export default function SalesOrderShipmentDetail() {
icon: 'calendar',
value_formatter: () => formatDate(shipment.delivery_date),
hidden: !shipment.delivery_date
},
{
type: 'link',
external: true,
name: 'link',
label: t`Link`,
copy: true,
hidden: !shipment.link
}
];
@@ -192,6 +219,8 @@ export default function SalesOrderShipmentDetail() {
</Grid.Col>
</Grid>
<DetailsTable fields={tr} item={data} />
<DetailsTable fields={bl} item={data} />
<DetailsTable fields={br} item={data} />
</ItemDetailsGrid>
</>
);
@@ -232,7 +261,8 @@ export default function SalesOrderShipmentDetail() {
}, [isPending, shipment, detailsPanel]);
const editShipmentFields = useSalesOrderShipmentFields({
pending: isPending
pending: isPending,
customerId: shipment.order_detail?.customer
});
const editShipment = useEditApiFormModal({

View File

@@ -33,8 +33,10 @@ import { DateColumn, LinkColumn } from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
export default function SalesOrderShipmentTable({
customerId,
orderId
}: Readonly<{
customerId: number;
orderId: number;
}>) {
const user = useUserState();
@@ -43,9 +45,13 @@ export default function SalesOrderShipmentTable({
const [selectedShipment, setSelectedShipment] = useState<any>({});
const newShipmentFields = useSalesOrderShipmentFields({});
const newShipmentFields = useSalesOrderShipmentFields({
customerId: customerId
});
const editShipmentFields = useSalesOrderShipmentFields({});
const editShipmentFields = useSalesOrderShipmentFields({
customerId: customerId
});
const completeShipmentFields = useSalesOrderShipmentCompleteFields({});