mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +00:00 
			
		
		
		
	[PUI] Sales order actions (#8086)
* [PUI] Add placeholder action - "Allocate Serials" action for sales order - No functionality yet * Implement form for allocating by serial numbers * Improve validation of serial numbers in back-end * Trim serial number string
This commit is contained in:
		| @@ -1517,37 +1517,45 @@ class SalesOrderSerialAllocationSerializer(serializers.Serializer): | ||||
|         except DjangoValidationError as e: | ||||
|             raise ValidationError({'serial_numbers': e.messages}) | ||||
|  | ||||
|         serials_not_exist = [] | ||||
|         serials_allocated = [] | ||||
|         serials_not_exist = set() | ||||
|         serials_unavailable = set() | ||||
|         stock_items_to_allocate = [] | ||||
|  | ||||
|         for serial in data['serials']: | ||||
|             serial = str(serial).strip() | ||||
|  | ||||
|             items = stock.models.StockItem.objects.filter( | ||||
|                 part=part, serial=serial, quantity=1 | ||||
|             ) | ||||
|  | ||||
|             if not items.exists(): | ||||
|                 serials_not_exist.append(str(serial)) | ||||
|                 serials_not_exist.add(str(serial)) | ||||
|                 continue | ||||
|  | ||||
|             stock_item = items[0] | ||||
|  | ||||
|             if stock_item.unallocated_quantity() == 1: | ||||
|                 stock_items_to_allocate.append(stock_item) | ||||
|             else: | ||||
|                 serials_allocated.append(str(serial)) | ||||
|             if not stock_item.in_stock: | ||||
|                 serials_unavailable.add(str(serial)) | ||||
|                 continue | ||||
|  | ||||
|             if stock_item.unallocated_quantity() < 1: | ||||
|                 serials_unavailable.add(str(serial)) | ||||
|                 continue | ||||
|  | ||||
|             # At this point, the serial number is valid, and can be added to the list | ||||
|             stock_items_to_allocate.append(stock_item) | ||||
|  | ||||
|         if len(serials_not_exist) > 0: | ||||
|             error_msg = _('No match found for the following serial numbers') | ||||
|             error_msg += ': ' | ||||
|             error_msg += ','.join(serials_not_exist) | ||||
|             error_msg += ','.join(sorted(serials_not_exist)) | ||||
|  | ||||
|             raise ValidationError({'serial_numbers': error_msg}) | ||||
|  | ||||
|         if len(serials_allocated) > 0: | ||||
|             error_msg = _('The following serial numbers are already allocated') | ||||
|         if len(serials_unavailable) > 0: | ||||
|             error_msg = _('The following serial numbers are unavailable') | ||||
|             error_msg += ': ' | ||||
|             error_msg += ','.join(serials_allocated) | ||||
|             error_msg += ','.join(sorted(serials_unavailable)) | ||||
|  | ||||
|             raise ValidationError({'serial_numbers': error_msg}) | ||||
|  | ||||
|   | ||||
| @@ -152,6 +152,7 @@ export enum ApiEndpoints { | ||||
|   sales_order_extra_line_list = 'order/so-extra-line/', | ||||
|   sales_order_allocation_list = 'order/so-allocation/', | ||||
|   sales_order_shipment_list = 'order/so/shipment/', | ||||
|   sales_order_allocate_serials = 'order/so/:id/allocate-serials/', | ||||
|  | ||||
|   return_order_list = 'order/ro/', | ||||
|   return_order_issue = 'order/ro/:id/issue/', | ||||
|   | ||||
| @@ -84,6 +84,31 @@ export function useSalesOrderLineItemFields({ | ||||
|   return fields; | ||||
| } | ||||
|  | ||||
| export function useSalesOrderAllocateSerialsFields({ | ||||
|   itemId, | ||||
|   orderId | ||||
| }: { | ||||
|   itemId: number; | ||||
|   orderId: number; | ||||
| }): ApiFormFieldSet { | ||||
|   return useMemo(() => { | ||||
|     return { | ||||
|       line_item: { | ||||
|         value: itemId, | ||||
|         hidden: true | ||||
|       }, | ||||
|       quantity: {}, | ||||
|       serial_numbers: {}, | ||||
|       shipment: { | ||||
|         filters: { | ||||
|           order: orderId, | ||||
|           shipped: false | ||||
|         } | ||||
|       } | ||||
|     }; | ||||
|   }, [itemId, orderId]); | ||||
| } | ||||
|  | ||||
| export function useSalesOrderShipmentFields(): ApiFormFieldSet { | ||||
|   return useMemo(() => { | ||||
|     return { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { t } from '@lingui/macro'; | ||||
| import { Text } from '@mantine/core'; | ||||
| import { | ||||
|   IconHash, | ||||
|   IconShoppingCart, | ||||
|   IconSquareArrowRight, | ||||
|   IconTools | ||||
| @@ -14,7 +15,10 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints'; | ||||
| import { ModelType } from '../../enums/ModelType'; | ||||
| import { UserRoles } from '../../enums/Roles'; | ||||
| import { useBuildOrderFields } from '../../forms/BuildForms'; | ||||
| import { useSalesOrderLineItemFields } from '../../forms/SalesOrderForms'; | ||||
| import { | ||||
|   useSalesOrderAllocateSerialsFields, | ||||
|   useSalesOrderLineItemFields | ||||
| } from '../../forms/SalesOrderForms'; | ||||
| import { notYetImplemented } from '../../functions/notifications'; | ||||
| import { | ||||
|   useCreateApiFormModal, | ||||
| @@ -223,6 +227,19 @@ export default function SalesOrderLineItemTable({ | ||||
|     table: table | ||||
|   }); | ||||
|  | ||||
|   const allocateSerialFields = useSalesOrderAllocateSerialsFields({ | ||||
|     itemId: selectedLine, | ||||
|     orderId: orderId | ||||
|   }); | ||||
|  | ||||
|   const allocateBySerials = useCreateApiFormModal({ | ||||
|     url: ApiEndpoints.sales_order_allocate_serials, | ||||
|     pk: orderId, | ||||
|     title: t`Allocate Serial Numbers`, | ||||
|     fields: allocateSerialFields, | ||||
|     table: table | ||||
|   }); | ||||
|  | ||||
|   const buildOrderFields = useBuildOrderFields({ create: true }); | ||||
|  | ||||
|   const newBuildOrder = useCreateApiFormModal({ | ||||
| @@ -264,6 +281,20 @@ export default function SalesOrderLineItemTable({ | ||||
|           color: 'green', | ||||
|           onClick: notYetImplemented | ||||
|         }, | ||||
|         { | ||||
|           hidden: | ||||
|             !record?.part_detail?.trackable || | ||||
|             allocated || | ||||
|             !editable || | ||||
|             !user.hasChangeRole(UserRoles.sales_order), | ||||
|           title: t`Allocate Serials`, | ||||
|           icon: <IconHash />, | ||||
|           color: 'green', | ||||
|           onClick: () => { | ||||
|             setSelectedLine(record.pk); | ||||
|             allocateBySerials.open(); | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           hidden: | ||||
|             allocated || | ||||
| @@ -323,6 +354,7 @@ export default function SalesOrderLineItemTable({ | ||||
|       {deleteLine.modal} | ||||
|       {newLine.modal} | ||||
|       {newBuildOrder.modal} | ||||
|       {allocateBySerials.modal} | ||||
|       <InvenTreeTable | ||||
|         url={apiUrl(ApiEndpoints.sales_order_line_list)} | ||||
|         tableState={table} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user