2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 19:46:46 +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:
Oliver 2024-09-06 14:33:16 +10:00 committed by GitHub
parent 3d9db2543d
commit 9f92475af0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 78 additions and 12 deletions

View File

@ -1517,37 +1517,45 @@ class SalesOrderSerialAllocationSerializer(serializers.Serializer):
except DjangoValidationError as e: except DjangoValidationError as e:
raise ValidationError({'serial_numbers': e.messages}) raise ValidationError({'serial_numbers': e.messages})
serials_not_exist = [] serials_not_exist = set()
serials_allocated = [] serials_unavailable = set()
stock_items_to_allocate = [] stock_items_to_allocate = []
for serial in data['serials']: for serial in data['serials']:
serial = str(serial).strip()
items = stock.models.StockItem.objects.filter( items = stock.models.StockItem.objects.filter(
part=part, serial=serial, quantity=1 part=part, serial=serial, quantity=1
) )
if not items.exists(): if not items.exists():
serials_not_exist.append(str(serial)) serials_not_exist.add(str(serial))
continue continue
stock_item = items[0] stock_item = items[0]
if stock_item.unallocated_quantity() == 1: 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) stock_items_to_allocate.append(stock_item)
else:
serials_allocated.append(str(serial))
if len(serials_not_exist) > 0: if len(serials_not_exist) > 0:
error_msg = _('No match found for the following serial numbers') error_msg = _('No match found for the following serial numbers')
error_msg += ': ' error_msg += ': '
error_msg += ','.join(serials_not_exist) error_msg += ','.join(sorted(serials_not_exist))
raise ValidationError({'serial_numbers': error_msg}) raise ValidationError({'serial_numbers': error_msg})
if len(serials_allocated) > 0: if len(serials_unavailable) > 0:
error_msg = _('The following serial numbers are already allocated') error_msg = _('The following serial numbers are unavailable')
error_msg += ': ' error_msg += ': '
error_msg += ','.join(serials_allocated) error_msg += ','.join(sorted(serials_unavailable))
raise ValidationError({'serial_numbers': error_msg}) raise ValidationError({'serial_numbers': error_msg})

View File

@ -152,6 +152,7 @@ export enum ApiEndpoints {
sales_order_extra_line_list = 'order/so-extra-line/', sales_order_extra_line_list = 'order/so-extra-line/',
sales_order_allocation_list = 'order/so-allocation/', sales_order_allocation_list = 'order/so-allocation/',
sales_order_shipment_list = 'order/so/shipment/', sales_order_shipment_list = 'order/so/shipment/',
sales_order_allocate_serials = 'order/so/:id/allocate-serials/',
return_order_list = 'order/ro/', return_order_list = 'order/ro/',
return_order_issue = 'order/ro/:id/issue/', return_order_issue = 'order/ro/:id/issue/',

View File

@ -84,6 +84,31 @@ export function useSalesOrderLineItemFields({
return fields; 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 { export function useSalesOrderShipmentFields(): ApiFormFieldSet {
return useMemo(() => { return useMemo(() => {
return { return {

View File

@ -1,6 +1,7 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Text } from '@mantine/core'; import { Text } from '@mantine/core';
import { import {
IconHash,
IconShoppingCart, IconShoppingCart,
IconSquareArrowRight, IconSquareArrowRight,
IconTools IconTools
@ -14,7 +15,10 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType'; import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles'; import { UserRoles } from '../../enums/Roles';
import { useBuildOrderFields } from '../../forms/BuildForms'; import { useBuildOrderFields } from '../../forms/BuildForms';
import { useSalesOrderLineItemFields } from '../../forms/SalesOrderForms'; import {
useSalesOrderAllocateSerialsFields,
useSalesOrderLineItemFields
} from '../../forms/SalesOrderForms';
import { notYetImplemented } from '../../functions/notifications'; import { notYetImplemented } from '../../functions/notifications';
import { import {
useCreateApiFormModal, useCreateApiFormModal,
@ -223,6 +227,19 @@ export default function SalesOrderLineItemTable({
table: table 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 buildOrderFields = useBuildOrderFields({ create: true });
const newBuildOrder = useCreateApiFormModal({ const newBuildOrder = useCreateApiFormModal({
@ -264,6 +281,20 @@ export default function SalesOrderLineItemTable({
color: 'green', color: 'green',
onClick: notYetImplemented 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: hidden:
allocated || allocated ||
@ -323,6 +354,7 @@ export default function SalesOrderLineItemTable({
{deleteLine.modal} {deleteLine.modal}
{newLine.modal} {newLine.modal}
{newBuildOrder.modal} {newBuildOrder.modal}
{allocateBySerials.modal}
<InvenTreeTable <InvenTreeTable
url={apiUrl(ApiEndpoints.sales_order_line_list)} url={apiUrl(ApiEndpoints.sales_order_line_list)}
tableState={table} tableState={table}