diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py
index a18e85c2c1..1468edc624 100644
--- a/src/backend/InvenTree/InvenTree/api_version.py
+++ b/src/backend/InvenTree/InvenTree/api_version.py
@@ -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
diff --git a/src/backend/InvenTree/order/serializers.py b/src/backend/InvenTree/order/serializers.py
index ee35d51127..bc993581c1 100644
--- a/src/backend/InvenTree/order/serializers.py
+++ b/src/backend/InvenTree/order/serializers.py
@@ -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
diff --git a/src/frontend/src/forms/SalesOrderForms.tsx b/src/frontend/src/forms/SalesOrderForms.tsx
index 7ca5f0d840..f696a731d3 100644
--- a/src/frontend/src/forms/SalesOrderForms.tsx
+++ b/src/frontend/src/forms/SalesOrderForms.tsx
@@ -378,7 +378,7 @@ export function useSalesOrderShipmentFields({
customerId,
pending
}: {
- customerId: number;
+ customerId?: number;
pending?: boolean;
}): ApiFormFieldSet {
return useMemo(() => {
diff --git a/src/frontend/src/pages/sales/SalesIndex.tsx b/src/frontend/src/pages/sales/SalesIndex.tsx
index 154e98593d..ffa1e1d5d3 100644
--- a/src/frontend/src/pages/sales/SalesIndex.tsx
+++ b/src/frontend/src/pages/sales/SalesIndex.tsx
@@ -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: ,
+ content: (
+
+ )
+ },
{
name: 'returnorders',
label: t`Return Orders`,
diff --git a/src/frontend/src/tables/sales/SalesOrderShipmentTable.tsx b/src/frontend/src/tables/sales/SalesOrderShipmentTable.tsx
index 4c6af541ea..7834c79e4c 100644
--- a/src/frontend/src/tables/sales/SalesOrderShipmentTable.tsx
+++ b/src/frontend/src/tables/sales/SalesOrderShipmentTable.tsx
@@ -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({});
@@ -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) => (
+
+ )
+ },
+ {
+ 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 [
];
- }, [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
}
}}
/>
diff --git a/src/frontend/tests/pages/pui_sales_order.spec.ts b/src/frontend/tests/pages/pui_sales_order.spec.ts
index 2a9546f958..5efea50a48 100644
--- a/src/frontend/tests/pages/pui_sales_order.spec.ts
+++ b/src/frontend/tests/pages/pui_sales_order.spec.ts
@@ -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');