2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-29 20:30:39 +00:00

[UI] Shipments table (#10675)

* Display PendingShipments panel

- Overview of all outstanding shipments

* Update UI tests

* Bump API version
This commit is contained in:
Oliver
2025-10-26 16:45:27 +11:00
committed by GitHub
parent 636477ac13
commit c54f3f4a30
6 changed files with 98 additions and 17 deletions

View File

@@ -1,11 +1,14 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 419
INVENTREE_API_VERSION = 420
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v420 -> 2025-10-26 : https://github.com/inventree/InvenTree/pull/10675
- Adds optional "customer_detail" filter to SalesOrderShipment API endpoint
v419 -> 2025-10-24 : https://github.com/inventree/InvenTree/pull/10659
- Fixes regression introduced in v417 which reverted the changes from v416

View File

@@ -1320,6 +1320,7 @@ class SalesOrderShipmentSerializer(
'notes',
# Extra detail fields
'checked_by_detail',
'customer_detail',
'order_detail',
'shipment_address_detail',
]
@@ -1352,6 +1353,13 @@ class SalesOrderShipmentSerializer(
True,
)
customer_detail = enable_filter(
CompanyBriefSerializer(
source='order.customer', many=False, read_only=True, allow_null=True
),
False,
)
shipment_address_detail = enable_filter(
AddressBriefSerializer(
source='shipment_address', many=False, read_only=True, allow_null=True

View File

@@ -378,7 +378,7 @@ export function useSalesOrderShipmentFields({
customerId,
pending
}: {
customerId: number;
customerId?: number;
pending?: boolean;
}): ApiFormFieldSet {
return useMemo(() => {

View File

@@ -3,6 +3,7 @@ import { Stack } from '@mantine/core';
import {
IconBuildingStore,
IconCalendar,
IconCubeSend,
IconTable,
IconTruckDelivery,
IconTruckReturn
@@ -20,6 +21,7 @@ import { PanelGroup } from '../../components/panels/PanelGroup';
import { useUserState } from '../../states/UserState';
import { CompanyTable } from '../../tables/company/CompanyTable';
import { ReturnOrderTable } from '../../tables/sales/ReturnOrderTable';
import SalesOrderShipmentTable from '../../tables/sales/SalesOrderShipmentTable';
import { SalesOrderTable } from '../../tables/sales/SalesOrderTable';
function SalesOrderOverview({
@@ -98,6 +100,18 @@ export default function SalesIndex() {
),
hidden: !user.hasViewRole(UserRoles.sales_order)
},
{
name: 'shipments',
label: t`Pending Shipments`,
icon: <IconCubeSend />,
content: (
<SalesOrderShipmentTable
tableName={'sales-order-pending-shipment'}
showOrderInfo
filters={{ shipped: false, order_outstanding: true }}
/>
)
},
{
name: 'returnorders',
label: t`Return Orders`,

View File

@@ -29,19 +29,30 @@ import {
} from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { useUserState } from '../../states/UserState';
import { DateColumn, LinkColumn } from '../ColumnRenderers';
import {
CompanyColumn,
DateColumn,
LinkColumn,
StatusColumn
} from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
export default function SalesOrderShipmentTable({
showOrderInfo = false,
tableName,
customerId,
orderId
orderId,
filters
}: Readonly<{
customerId: number;
orderId: number;
showOrderInfo?: boolean;
tableName?: string;
customerId?: number;
orderId?: number;
filters?: any;
}>) {
const user = useUserState();
const navigate = useNavigate();
const table = useTable('sales-order-shipment');
const table = useTable(tableName ?? 'sales-order-shipment');
const [selectedShipment, setSelectedShipment] = useState<any>({});
@@ -95,6 +106,30 @@ export default function SalesOrderShipmentTable({
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'customer',
title: t`Customer`,
switchable: true,
sortable: true,
hidden: !showOrderInfo,
render: (record: any) => (
<CompanyColumn company={record.customer_detail} />
)
},
{
switchable: false,
accessor: 'order_detail.reference',
title: t`Sales Order`,
hidden: !showOrderInfo,
sortable: false
},
StatusColumn({
switchable: true,
model: ModelType.salesorder,
accessor: 'order_detail.status',
title: t`Order Status`,
hidden: !showOrderInfo
}),
{
accessor: 'reference',
title: t`Shipment Reference`,
@@ -146,19 +181,13 @@ export default function SalesOrderShipmentTable({
accessor: 'link'
})
];
}, []);
}, [showOrderInfo]);
const rowActions = useCallback(
(record: any): RowAction[] => {
const shipped: boolean = !!record.shipment_date;
return [
RowViewAction({
title: t`View Shipment`,
modelType: ModelType.salesordershipment,
modelId: record.pk,
navigate: navigate
}),
{
hidden: shipped || !user.hasChangeRole(UserRoles.sales_order),
title: t`Complete Shipment`,
@@ -184,13 +213,28 @@ export default function SalesOrderShipmentTable({
setSelectedShipment(record);
deleteShipment.open();
}
}),
RowViewAction({
title: t`View Sales Order`,
modelType: ModelType.salesorder,
modelId: record.order,
hidden:
!record.order ||
!showOrderInfo ||
!user.hasViewRole(UserRoles.sales_order),
navigate: navigate
})
];
},
[user]
[showOrderInfo, user]
);
const tableActions = useMemo(() => {
// No actions possible if no order is specified
if (!orderId) {
return [];
}
return [
<AddItemButton
key='add-shipment'
@@ -201,7 +245,7 @@ export default function SalesOrderShipmentTable({
}}
/>
];
}, [user]);
}, [orderId, user]);
const tableFilters: TableFilter[] = useMemo(() => {
return [
@@ -241,7 +285,10 @@ export default function SalesOrderShipmentTable({
enableReports: true,
rowActions: rowActions,
params: {
order: orderId
order: orderId,
order_detail: true,
customer_detail: showOrderInfo,
...filters
}
}}
/>

View File

@@ -14,9 +14,18 @@ test('Sales Orders - Tabs', async ({ browser }) => {
await page.waitForURL('**/web/sales/**');
// Sales Orders panel
await loadTab(page, 'Sales Orders');
await page.waitForURL('**/web/sales/index/salesorders');
// Pending Shipments panel
await loadTab(page, 'Pending Shipments');
await page.getByRole('cell', { name: 'SO0007' }).waitFor();
await page.getByRole('button', { name: 'Shipment Reference' }).waitFor();
// Return Orders panel
await loadTab(page, 'Return Orders');
await page.getByRole('cell', { name: 'NOISE-COMPLAINT' }).waitFor();
// Customers
await loadTab(page, 'Customers');