mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 05:05: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:
		| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -378,7 +378,7 @@ export function useSalesOrderShipmentFields({ | ||||
|   customerId, | ||||
|   pending | ||||
| }: { | ||||
|   customerId: number; | ||||
|   customerId?: number; | ||||
|   pending?: boolean; | ||||
| }): ApiFormFieldSet { | ||||
|   return useMemo(() => { | ||||
|   | ||||
| @@ -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`, | ||||
|   | ||||
| @@ -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 | ||||
|           } | ||||
|         }} | ||||
|       /> | ||||
|   | ||||
| @@ -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'); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user