2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-16 12:05:53 +00:00
Files
InvenTree/src/frontend/src/pages/sales/SalesOrderShipmentDetail.tsx
Oliver 95874d9097 Update SalesOrderAllocationTable (#8911)
* Update SalesOrderAllocationTable

- Add 'description' column for part
- Add 'IPN' column for part

* Fix unit test
2025-01-21 00:37:36 +11:00

384 lines
11 KiB
TypeScript

import { t } from '@lingui/macro';
import { Grid, Skeleton, Stack } from '@mantine/core';
import { IconBookmark, IconInfoCircle } from '@tabler/icons-react';
import { useMemo } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import PrimaryActionButton from '../../components/buttons/PrimaryActionButton';
import { PrintingActions } from '../../components/buttons/PrintingActions';
import {
type DetailsField,
DetailsTable
} from '../../components/details/Details';
import DetailsBadge from '../../components/details/DetailsBadge';
import { DetailsImage } from '../../components/details/DetailsImage';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
BarcodeActionDropdown,
CancelItemAction,
EditItemAction,
OptionsActionDropdown
} from '../../components/items/ActionDropdown';
import InstanceDetail from '../../components/nav/InstanceDetail';
import { PageDetail } from '../../components/nav/PageDetail';
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 { formatDate } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import {
useSalesOrderShipmentCompleteFields,
useSalesOrderShipmentFields
} from '../../forms/SalesOrderForms';
import { getDetailUrl } from '../../functions/urls';
import {
useCreateApiFormModal,
useDeleteApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance';
import { useUserState } from '../../states/UserState';
import SalesOrderAllocationTable from '../../tables/sales/SalesOrderAllocationTable';
export default function SalesOrderShipmentDetail() {
const { id } = useParams();
const user = useUserState();
const navigate = useNavigate();
const {
instance: shipment,
instanceQuery: shipmentQuery,
refreshInstance: refreshShipment,
requestStatus: shipmentStatus
} = useInstance({
endpoint: ApiEndpoints.sales_order_shipment_list,
pk: id,
params: {
order_detail: true
}
});
const {
instance: customer,
instanceQuery: customerQuery,
refreshInstance: refreshCustomer,
requestStatus: customerStatus
} = useInstance({
endpoint: ApiEndpoints.company_list,
pk: shipment.order_detail?.customer,
hasPrimaryKey: true
});
const isPending = useMemo(() => !shipment.shipment_date, [shipment]);
const detailsPanel = useMemo(() => {
if (shipmentQuery.isFetching || customerQuery.isFetching) {
return <Skeleton />;
}
const data: any = {
...shipment,
customer: customer?.pk,
customer_name: customer?.name,
customer_reference: shipment.order_detail?.customer_reference
};
// Top Left: Order / customer information
const tl: DetailsField[] = [
{
type: 'link',
model: ModelType.salesorder,
name: 'order',
label: t`Sales Order`,
icon: 'sales_orders',
model_field: 'reference'
},
{
type: 'link',
name: 'customer',
icon: 'customers',
label: t`Customer`,
model: ModelType.company,
model_field: 'name',
hidden: !data.customer
},
{
type: 'text',
name: 'customer_reference',
icon: 'serial',
label: t`Customer Reference`,
hidden: !data.customer_reference,
copy: true
},
{
type: 'text',
name: 'reference',
icon: 'serial',
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',
label: t`Tracking Number`,
icon: 'trackable',
value_formatter: () => shipment.tracking_number || '---',
copy: !!shipment.tracking_number
},
{
type: 'text',
name: 'invoice_number',
label: t`Invoice Number`,
icon: 'serial',
value_formatter: () => shipment.invoice_number || '---',
copy: !!shipment.invoice_number
},
{
type: 'text',
name: 'shipment_date',
label: t`Shipment Date`,
icon: 'calendar',
value_formatter: () => formatDate(shipment.shipment_date),
hidden: !shipment.shipment_date
},
{
type: 'text',
name: 'delivery_date',
label: t`Delivery Date`,
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
}
];
return (
<>
<ItemDetailsGrid>
<Grid grow>
<DetailsImage
appRole={UserRoles.sales_order}
apiPath={ApiEndpoints.company_list}
src={customer?.image}
pk={customer?.pk}
imageActions={{
selectExisting: false,
downloadImage: false,
uploadFile: false,
deleteFile: false
}}
/>
<Grid.Col span={{ base: 12, sm: 8 }}>
<DetailsTable fields={tl} item={data} />
</Grid.Col>
</Grid>
<DetailsTable fields={tr} item={data} />
</ItemDetailsGrid>
</>
);
}, [shipment, shipmentQuery, customer, customerQuery]);
const shipmentPanels: PanelType[] = useMemo(() => {
return [
{
name: 'detail',
label: t`Shipment Details`,
icon: <IconInfoCircle />,
content: detailsPanel
},
{
name: 'items',
label: t`Allocated Stock`,
icon: <IconBookmark />,
content: (
<SalesOrderAllocationTable
shipmentId={shipment.pk}
showPartInfo
allowEdit={isPending}
modelField='item'
modelTarget={ModelType.stockitem}
/>
)
},
AttachmentPanel({
model_type: ModelType.salesordershipment,
model_id: shipment.pk
}),
NotesPanel({
model_type: ModelType.salesordershipment,
model_id: shipment.pk
})
];
}, [isPending, shipment, detailsPanel]);
const editShipmentFields = useSalesOrderShipmentFields({
pending: isPending
});
const editShipment = useEditApiFormModal({
url: ApiEndpoints.sales_order_shipment_list,
pk: shipment.pk,
fields: editShipmentFields,
title: t`Edit Shipment`,
onFormSuccess: refreshShipment
});
const deleteShipment = useDeleteApiFormModal({
url: ApiEndpoints.sales_order_shipment_list,
pk: shipment.pk,
title: t`Cancel Shipment`,
onFormSuccess: () => {
// Shipment has been deleted - navigate back to the sales order
navigate(getDetailUrl(ModelType.salesorder, shipment.order));
}
});
const completeShipmentFields = useSalesOrderShipmentCompleteFields({});
const completeShipment = useCreateApiFormModal({
url: ApiEndpoints.sales_order_shipment_complete,
pk: shipment.pk,
fields: completeShipmentFields,
title: t`Complete Shipment`,
focus: 'tracking_number',
initialData: {
...shipment,
shipment_date: new Date().toISOString().split('T')[0]
},
onFormSuccess: refreshShipment
});
const shipmentBadges = useMemo(() => {
if (shipmentQuery.isFetching) {
return [];
}
return [
<DetailsBadge
key='pending'
label={t`Pending`}
color='gray'
visible={isPending}
/>,
<DetailsBadge
key='shipped'
label={t`Shipped`}
color='green'
visible={!isPending}
/>,
<DetailsBadge
key='delivered'
label={t`Delivered`}
color='blue'
visible={!!shipment.delivery_date}
/>
];
}, [isPending, shipment.deliveryDate, shipmentQuery.isFetching]);
const shipmentActions = useMemo(() => {
const canEdit: boolean = user.hasChangePermission(
ModelType.salesordershipment
);
return [
<PrimaryActionButton
key='send-shipment'
title={t`Send Shipment`}
icon='sales_orders'
hidden={!isPending}
color='green'
onClick={() => {
completeShipment.open();
}}
/>,
<BarcodeActionDropdown
key='barcode'
model={ModelType.salesordershipment}
pk={shipment.pk}
/>,
<PrintingActions
key='print'
modelType={ModelType.salesordershipment}
items={[shipment.pk]}
enableLabels
enableReports
/>,
<OptionsActionDropdown
key='actions'
tooltip={t`Shipment Actions`}
actions={[
EditItemAction({
hidden: !canEdit,
onClick: editShipment.open,
tooltip: t`Edit Shipment`
}),
CancelItemAction({
hidden: !isPending,
onClick: deleteShipment.open,
tooltip: t`Cancel Shipment`
})
]}
/>
];
}, [isPending, user, shipment]);
return (
<>
{completeShipment.modal}
{editShipment.modal}
{deleteShipment.modal}
<InstanceDetail
status={shipmentStatus}
loading={shipmentQuery.isFetching || customerQuery.isFetching}
requiredRole={UserRoles.sales_order}
>
<Stack gap='xs'>
<PageDetail
title={`${t`Sales Order Shipment`}: ${shipment.reference}`}
subtitle={`${t`Sales Order`}: ${shipment.order_detail?.reference}`}
breadcrumbs={[
{ name: t`Sales`, url: '/sales/' },
{
name: shipment.order_detail?.reference,
url: getDetailUrl(ModelType.salesorder, shipment.order)
}
]}
badges={shipmentBadges}
imageUrl={customer?.image}
editAction={editShipment.open}
editEnabled={user.hasChangePermission(ModelType.salesordershipment)}
actions={shipmentActions}
/>
<PanelGroup
pageKey='salesordershipment'
panels={shipmentPanels}
model={ModelType.salesordershipment}
instance={shipment}
id={shipment.pk}
/>
</Stack>
</InstanceDetail>
</>
);
}