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:
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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({});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user