2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-30 04:35:42 +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 information."""
# InvenTree API version # 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.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ 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 v419 -> 2025-10-24 : https://github.com/inventree/InvenTree/pull/10659
- Fixes regression introduced in v417 which reverted the changes from v416 - Fixes regression introduced in v417 which reverted the changes from v416

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ import { Stack } from '@mantine/core';
import { import {
IconBuildingStore, IconBuildingStore,
IconCalendar, IconCalendar,
IconCubeSend,
IconTable, IconTable,
IconTruckDelivery, IconTruckDelivery,
IconTruckReturn IconTruckReturn
@@ -20,6 +21,7 @@ import { PanelGroup } from '../../components/panels/PanelGroup';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import { CompanyTable } from '../../tables/company/CompanyTable'; import { CompanyTable } from '../../tables/company/CompanyTable';
import { ReturnOrderTable } from '../../tables/sales/ReturnOrderTable'; import { ReturnOrderTable } from '../../tables/sales/ReturnOrderTable';
import SalesOrderShipmentTable from '../../tables/sales/SalesOrderShipmentTable';
import { SalesOrderTable } from '../../tables/sales/SalesOrderTable'; import { SalesOrderTable } from '../../tables/sales/SalesOrderTable';
function SalesOrderOverview({ function SalesOrderOverview({
@@ -98,6 +100,18 @@ export default function SalesIndex() {
), ),
hidden: !user.hasViewRole(UserRoles.sales_order) 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', name: 'returnorders',
label: t`Return Orders`, label: t`Return Orders`,

View File

@@ -29,19 +29,30 @@ import {
} from '../../hooks/UseForm'; } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable'; import { useTable } from '../../hooks/UseTable';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import { DateColumn, LinkColumn } from '../ColumnRenderers'; import {
CompanyColumn,
DateColumn,
LinkColumn,
StatusColumn
} from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable'; import { InvenTreeTable } from '../InvenTreeTable';
export default function SalesOrderShipmentTable({ export default function SalesOrderShipmentTable({
showOrderInfo = false,
tableName,
customerId, customerId,
orderId orderId,
filters
}: Readonly<{ }: Readonly<{
customerId: number; showOrderInfo?: boolean;
orderId: number; tableName?: string;
customerId?: number;
orderId?: number;
filters?: any;
}>) { }>) {
const user = useUserState(); const user = useUserState();
const navigate = useNavigate(); const navigate = useNavigate();
const table = useTable('sales-order-shipment'); const table = useTable(tableName ?? 'sales-order-shipment');
const [selectedShipment, setSelectedShipment] = useState<any>({}); const [selectedShipment, setSelectedShipment] = useState<any>({});
@@ -95,6 +106,30 @@ export default function SalesOrderShipmentTable({
const tableColumns: TableColumn[] = useMemo(() => { const tableColumns: TableColumn[] = useMemo(() => {
return [ 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', accessor: 'reference',
title: t`Shipment Reference`, title: t`Shipment Reference`,
@@ -146,19 +181,13 @@ export default function SalesOrderShipmentTable({
accessor: 'link' accessor: 'link'
}) })
]; ];
}, []); }, [showOrderInfo]);
const rowActions = useCallback( const rowActions = useCallback(
(record: any): RowAction[] => { (record: any): RowAction[] => {
const shipped: boolean = !!record.shipment_date; const shipped: boolean = !!record.shipment_date;
return [ return [
RowViewAction({
title: t`View Shipment`,
modelType: ModelType.salesordershipment,
modelId: record.pk,
navigate: navigate
}),
{ {
hidden: shipped || !user.hasChangeRole(UserRoles.sales_order), hidden: shipped || !user.hasChangeRole(UserRoles.sales_order),
title: t`Complete Shipment`, title: t`Complete Shipment`,
@@ -184,13 +213,28 @@ export default function SalesOrderShipmentTable({
setSelectedShipment(record); setSelectedShipment(record);
deleteShipment.open(); 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(() => { const tableActions = useMemo(() => {
// No actions possible if no order is specified
if (!orderId) {
return [];
}
return [ return [
<AddItemButton <AddItemButton
key='add-shipment' key='add-shipment'
@@ -201,7 +245,7 @@ export default function SalesOrderShipmentTable({
}} }}
/> />
]; ];
}, [user]); }, [orderId, user]);
const tableFilters: TableFilter[] = useMemo(() => { const tableFilters: TableFilter[] = useMemo(() => {
return [ return [
@@ -241,7 +285,10 @@ export default function SalesOrderShipmentTable({
enableReports: true, enableReports: true,
rowActions: rowActions, rowActions: rowActions,
params: { 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/**'); await page.waitForURL('**/web/sales/**');
// Sales Orders panel
await loadTab(page, 'Sales Orders'); await loadTab(page, 'Sales Orders');
await page.waitForURL('**/web/sales/index/salesorders'); 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 loadTab(page, 'Return Orders');
await page.getByRole('cell', { name: 'NOISE-COMPLAINT' }).waitFor();
// Customers // Customers
await loadTab(page, 'Customers'); await loadTab(page, 'Customers');