mirror of
https://github.com/inventree/InvenTree.git
synced 2025-10-30 12:45:42 +00:00
[UI] Implement "checked_by" for SalesOrderShipment (#10654)
* Add "checked" column to SalesOrderStatus table * Add API filter for "checked" status * Add Checked / Not Checked badge * Add actions to check / uncheck shipment * Add modal for changing checked_by status * Display checked_by user * Tweak wording * Bump API version * Update CHANGELOG file * Update docs * Add new global setting - Prevent shipment completion which have not been checked * Test if shipment has been checked * Updated unit tests * Updated type hinting (may as well while I'm here) * Adjust shipment icon * Add "order_outstanding" filter for SalesOrderShipment table
This commit is contained in:
@@ -31,6 +31,7 @@ import {
|
||||
IconCornerDownLeft,
|
||||
IconCornerDownRight,
|
||||
IconCornerUpRightDouble,
|
||||
IconCubeSend,
|
||||
IconCurrencyDollar,
|
||||
IconDots,
|
||||
IconEdit,
|
||||
@@ -154,7 +155,7 @@ const icons: InvenTreeIconType = {
|
||||
sales_orders: IconTruckDelivery,
|
||||
scheduling: IconCalendarStats,
|
||||
scrap: IconCircleX,
|
||||
shipment: IconTruckDelivery,
|
||||
shipment: IconCubeSend,
|
||||
test_templates: IconTestPipe,
|
||||
test: IconTestPipe,
|
||||
related_parts: IconLayersLinked,
|
||||
|
||||
@@ -305,7 +305,8 @@ export default function SystemSettings() {
|
||||
'SALESORDER_REQUIRE_RESPONSIBLE',
|
||||
'SALESORDER_DEFAULT_SHIPMENT',
|
||||
'SALESORDER_EDIT_COMPLETED_ORDERS',
|
||||
'SALESORDER_SHIP_COMPLETE'
|
||||
'SALESORDER_SHIP_COMPLETE',
|
||||
'SALESORDER_SHIPMENT_REQUIRES_CHECK'
|
||||
]}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -2,10 +2,10 @@ import { t } from '@lingui/core/macro';
|
||||
import { Accordion, Grid, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import {
|
||||
IconBookmark,
|
||||
IconCubeSend,
|
||||
IconInfoCircle,
|
||||
IconList,
|
||||
IconTools,
|
||||
IconTruckDelivery
|
||||
IconTools
|
||||
} from '@tabler/icons-react';
|
||||
import { type ReactNode, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -394,7 +394,7 @@ export default function SalesOrderDetail() {
|
||||
{
|
||||
name: 'shipments',
|
||||
label: t`Shipments`,
|
||||
icon: <IconTruckDelivery />,
|
||||
icon: <IconCubeSend />,
|
||||
content: (
|
||||
<SalesOrderShipmentTable
|
||||
orderId={order.pk}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Grid, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { IconBookmark, IconInfoCircle } from '@tabler/icons-react';
|
||||
import { Alert, Grid, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import {
|
||||
IconBookmark,
|
||||
IconCircleCheck,
|
||||
IconCircleX,
|
||||
IconInfoCircle
|
||||
} from '@tabler/icons-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
@@ -31,6 +36,7 @@ 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 { RenderUser } from '../../components/render/User';
|
||||
import { formatDate } from '../../defaults/formatters';
|
||||
import {
|
||||
useSalesOrderShipmentCompleteFields,
|
||||
@@ -50,6 +56,8 @@ export default function SalesOrderShipmentDetail() {
|
||||
const user = useUserState();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const userId = useMemo(() => user.userId(), [user]);
|
||||
|
||||
const {
|
||||
instance: shipment,
|
||||
instanceQuery: shipmentQuery,
|
||||
@@ -74,6 +82,8 @@ export default function SalesOrderShipmentDetail() {
|
||||
|
||||
const isPending = useMemo(() => !shipment.shipment_date, [shipment]);
|
||||
|
||||
const isChecked = useMemo(() => !!shipment.checked_by, [shipment]);
|
||||
|
||||
const detailsPanel = useMemo(() => {
|
||||
if (shipmentQuery.isFetching || customerQuery.isFetching) {
|
||||
return <Skeleton />;
|
||||
@@ -180,6 +190,18 @@ export default function SalesOrderShipmentDetail() {
|
||||
icon: 'packages',
|
||||
label: t`Allocated Items`
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'checked_by',
|
||||
label: t`Checked By`,
|
||||
icon: 'check',
|
||||
value_formatter: () =>
|
||||
shipment.checked_by_detail ? (
|
||||
<RenderUser instance={shipment.checked_by_detail} />
|
||||
) : (
|
||||
<Text size='sm' c='red'>{t`Not checked`}</Text>
|
||||
)
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'shipment_date',
|
||||
@@ -298,6 +320,46 @@ export default function SalesOrderShipmentDetail() {
|
||||
onFormSuccess: refreshShipment
|
||||
});
|
||||
|
||||
const checkShipment = useEditApiFormModal({
|
||||
url: ApiEndpoints.sales_order_shipment_list,
|
||||
pk: shipment.pk,
|
||||
title: t`Check Shipment`,
|
||||
preFormContent: (
|
||||
<Alert color='green' icon={<IconCircleCheck />} title={t`Check Shipment`}>
|
||||
<Text>{t`Marking the shipment as checked indicates that you have verified that all items included in this shipment are correct`}</Text>
|
||||
</Alert>
|
||||
),
|
||||
fetchInitialData: false,
|
||||
fields: {
|
||||
checked_by: {
|
||||
hidden: true,
|
||||
value: userId
|
||||
}
|
||||
},
|
||||
successMessage: t`Shipment marked as checked`,
|
||||
onFormSuccess: refreshShipment
|
||||
});
|
||||
|
||||
const uncheckShipment = useEditApiFormModal({
|
||||
url: ApiEndpoints.sales_order_shipment_list,
|
||||
pk: shipment.pk,
|
||||
title: t`Uncheck Shipment`,
|
||||
preFormContent: (
|
||||
<Alert color='red' icon={<IconCircleX />} title={t`Uncheck Shipment`}>
|
||||
<Text>{t`Marking the shipment as unchecked indicates that the shipment requires further verification`}</Text>
|
||||
</Alert>
|
||||
),
|
||||
fetchInitialData: false,
|
||||
fields: {
|
||||
checked_by: {
|
||||
hidden: true,
|
||||
value: null
|
||||
}
|
||||
},
|
||||
successMessage: t`Shipment marked as unchecked`,
|
||||
onFormSuccess: refreshShipment
|
||||
});
|
||||
|
||||
const shipmentBadges = useMemo(() => {
|
||||
if (shipmentQuery.isFetching) {
|
||||
return [];
|
||||
@@ -310,6 +372,18 @@ export default function SalesOrderShipmentDetail() {
|
||||
color='gray'
|
||||
visible={isPending}
|
||||
/>,
|
||||
<DetailsBadge
|
||||
key='checked'
|
||||
label={t`Checked`}
|
||||
color='green'
|
||||
visible={isPending && isChecked}
|
||||
/>,
|
||||
<DetailsBadge
|
||||
key='not-checked'
|
||||
label={t`Not Checked`}
|
||||
color='red'
|
||||
visible={isPending && !isChecked}
|
||||
/>,
|
||||
<DetailsBadge
|
||||
key='shipped'
|
||||
label={t`Shipped`}
|
||||
@@ -323,7 +397,7 @@ export default function SalesOrderShipmentDetail() {
|
||||
visible={!!shipment.delivery_date}
|
||||
/>
|
||||
];
|
||||
}, [isPending, shipment.deliveryDate, shipmentQuery.isFetching]);
|
||||
}, [isPending, isChecked, shipment.deliveryDate, shipmentQuery.isFetching]);
|
||||
|
||||
const shipmentActions = useMemo(() => {
|
||||
const canEdit: boolean = user.hasChangePermission(
|
||||
@@ -363,6 +437,20 @@ export default function SalesOrderShipmentDetail() {
|
||||
onClick: editShipment.open,
|
||||
tooltip: t`Edit Shipment`
|
||||
}),
|
||||
{
|
||||
hidden: !isPending || isChecked,
|
||||
name: t`Check`,
|
||||
tooltip: t`Mark shipment as checked`,
|
||||
icon: <IconCircleCheck color='green' />,
|
||||
onClick: checkShipment.open
|
||||
},
|
||||
{
|
||||
hidden: !isPending || !isChecked,
|
||||
name: t`Uncheck`,
|
||||
tooltip: t`Mark shipment as unchecked`,
|
||||
icon: <IconCircleX color='red' />,
|
||||
onClick: uncheckShipment.open
|
||||
},
|
||||
CancelItemAction({
|
||||
hidden: !isPending,
|
||||
onClick: deleteShipment.open,
|
||||
@@ -371,13 +459,15 @@ export default function SalesOrderShipmentDetail() {
|
||||
]}
|
||||
/>
|
||||
];
|
||||
}, [isPending, user, shipment]);
|
||||
}, [isChecked, isPending, user, shipment]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{completeShipment.modal}
|
||||
{editShipment.modal}
|
||||
{deleteShipment.modal}
|
||||
{checkShipment.modal}
|
||||
{uncheckShipment.modal}
|
||||
<InstanceDetail
|
||||
query={shipmentQuery}
|
||||
requiredRole={UserRoles.sales_order}
|
||||
|
||||
@@ -107,6 +107,13 @@ export default function SalesOrderShipmentTable({
|
||||
switchable: false,
|
||||
title: t`Items`
|
||||
},
|
||||
{
|
||||
accessor: 'checked',
|
||||
title: t`Checked`,
|
||||
switchable: true,
|
||||
sortable: false,
|
||||
render: (record: any) => <YesNoButton value={!!record.checked_by} />
|
||||
},
|
||||
{
|
||||
accessor: 'shipped',
|
||||
title: t`Shipped`,
|
||||
@@ -114,6 +121,13 @@ export default function SalesOrderShipmentTable({
|
||||
sortable: false,
|
||||
render: (record: any) => <YesNoButton value={!!record.shipment_date} />
|
||||
},
|
||||
{
|
||||
accessor: 'delivered',
|
||||
title: t`Delivered`,
|
||||
switchable: true,
|
||||
sortable: false,
|
||||
render: (record: any) => <YesNoButton value={!!record.delivery_date} />
|
||||
},
|
||||
DateColumn({
|
||||
accessor: 'shipment_date',
|
||||
title: t`Shipment Date`
|
||||
@@ -191,6 +205,11 @@ export default function SalesOrderShipmentTable({
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
name: 'checked',
|
||||
label: t`Checked`,
|
||||
description: t`Show shipments which have been checked`
|
||||
},
|
||||
{
|
||||
name: 'shipped',
|
||||
label: t`Shipped`,
|
||||
|
||||
Reference in New Issue
Block a user