mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-03 22:55:43 +00:00 
			
		
		
		
	[PUI] Fix stock actions (#8569)
* Fix useAssignStockItems - Only allow "salable" parts * Assign item to customer * Adjust playwright tests
This commit is contained in:
		@@ -870,6 +870,7 @@ function stockOperationModal({
 | 
				
			|||||||
  endpoint,
 | 
					  endpoint,
 | 
				
			||||||
  filters,
 | 
					  filters,
 | 
				
			||||||
  title,
 | 
					  title,
 | 
				
			||||||
 | 
					  successMessage,
 | 
				
			||||||
  modalFunc = useCreateApiFormModal
 | 
					  modalFunc = useCreateApiFormModal
 | 
				
			||||||
}: {
 | 
					}: {
 | 
				
			||||||
  items?: object;
 | 
					  items?: object;
 | 
				
			||||||
@@ -880,6 +881,7 @@ function stockOperationModal({
 | 
				
			|||||||
  fieldGenerator: (items: any[]) => ApiFormFieldSet;
 | 
					  fieldGenerator: (items: any[]) => ApiFormFieldSet;
 | 
				
			||||||
  endpoint: ApiEndpoints;
 | 
					  endpoint: ApiEndpoints;
 | 
				
			||||||
  title: string;
 | 
					  title: string;
 | 
				
			||||||
 | 
					  successMessage?: string;
 | 
				
			||||||
  modalFunc?: apiModalFunc;
 | 
					  modalFunc?: apiModalFunc;
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
  const baseParams: any = {
 | 
					  const baseParams: any = {
 | 
				
			||||||
@@ -932,12 +934,13 @@ function stockOperationModal({
 | 
				
			|||||||
    fields: fields,
 | 
					    fields: fields,
 | 
				
			||||||
    title: title,
 | 
					    title: title,
 | 
				
			||||||
    size: '80%',
 | 
					    size: '80%',
 | 
				
			||||||
 | 
					    successMessage: successMessage,
 | 
				
			||||||
    onFormSuccess: () => refresh()
 | 
					    onFormSuccess: () => refresh()
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type StockOperationProps = {
 | 
					export type StockOperationProps = {
 | 
				
			||||||
  items?: object;
 | 
					  items?: any[];
 | 
				
			||||||
  pk?: number;
 | 
					  pk?: number;
 | 
				
			||||||
  filters?: any;
 | 
					  filters?: any;
 | 
				
			||||||
  model: ModelType.stockitem | 'location' | ModelType.part;
 | 
					  model: ModelType.stockitem | 'location' | ModelType.part;
 | 
				
			||||||
@@ -949,7 +952,8 @@ export function useAddStockItem(props: StockOperationProps) {
 | 
				
			|||||||
    ...props,
 | 
					    ...props,
 | 
				
			||||||
    fieldGenerator: stockAddFields,
 | 
					    fieldGenerator: stockAddFields,
 | 
				
			||||||
    endpoint: ApiEndpoints.stock_add,
 | 
					    endpoint: ApiEndpoints.stock_add,
 | 
				
			||||||
    title: t`Add Stock`
 | 
					    title: t`Add Stock`,
 | 
				
			||||||
 | 
					    successMessage: t`Stock added`
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -958,7 +962,8 @@ export function useRemoveStockItem(props: StockOperationProps) {
 | 
				
			|||||||
    ...props,
 | 
					    ...props,
 | 
				
			||||||
    fieldGenerator: stockRemoveFields,
 | 
					    fieldGenerator: stockRemoveFields,
 | 
				
			||||||
    endpoint: ApiEndpoints.stock_remove,
 | 
					    endpoint: ApiEndpoints.stock_remove,
 | 
				
			||||||
    title: t`Remove Stock`
 | 
					    title: t`Remove Stock`,
 | 
				
			||||||
 | 
					    successMessage: t`Stock removed`
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -967,7 +972,8 @@ export function useTransferStockItem(props: StockOperationProps) {
 | 
				
			|||||||
    ...props,
 | 
					    ...props,
 | 
				
			||||||
    fieldGenerator: stockTransferFields,
 | 
					    fieldGenerator: stockTransferFields,
 | 
				
			||||||
    endpoint: ApiEndpoints.stock_transfer,
 | 
					    endpoint: ApiEndpoints.stock_transfer,
 | 
				
			||||||
    title: t`Transfer Stock`
 | 
					    title: t`Transfer Stock`,
 | 
				
			||||||
 | 
					    successMessage: t`Stock transferred`
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -976,7 +982,8 @@ export function useCountStockItem(props: StockOperationProps) {
 | 
				
			|||||||
    ...props,
 | 
					    ...props,
 | 
				
			||||||
    fieldGenerator: stockCountFields,
 | 
					    fieldGenerator: stockCountFields,
 | 
				
			||||||
    endpoint: ApiEndpoints.stock_count,
 | 
					    endpoint: ApiEndpoints.stock_count,
 | 
				
			||||||
    title: t`Count Stock`
 | 
					    title: t`Count Stock`,
 | 
				
			||||||
 | 
					    successMessage: t`Stock counted`
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -985,7 +992,8 @@ export function useChangeStockStatus(props: StockOperationProps) {
 | 
				
			|||||||
    ...props,
 | 
					    ...props,
 | 
				
			||||||
    fieldGenerator: stockChangeStatusFields,
 | 
					    fieldGenerator: stockChangeStatusFields,
 | 
				
			||||||
    endpoint: ApiEndpoints.stock_change_status,
 | 
					    endpoint: ApiEndpoints.stock_change_status,
 | 
				
			||||||
    title: t`Change Stock Status`
 | 
					    title: t`Change Stock Status`,
 | 
				
			||||||
 | 
					    successMessage: t`Stock status changed`
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -994,16 +1002,24 @@ export function useMergeStockItem(props: StockOperationProps) {
 | 
				
			|||||||
    ...props,
 | 
					    ...props,
 | 
				
			||||||
    fieldGenerator: stockMergeFields,
 | 
					    fieldGenerator: stockMergeFields,
 | 
				
			||||||
    endpoint: ApiEndpoints.stock_merge,
 | 
					    endpoint: ApiEndpoints.stock_merge,
 | 
				
			||||||
    title: t`Merge Stock`
 | 
					    title: t`Merge Stock`,
 | 
				
			||||||
 | 
					    successMessage: t`Stock merged`
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useAssignStockItem(props: StockOperationProps) {
 | 
					export function useAssignStockItem(props: StockOperationProps) {
 | 
				
			||||||
 | 
					  // Filter items - only allow 'salable' items
 | 
				
			||||||
 | 
					  const items = useMemo(() => {
 | 
				
			||||||
 | 
					    return props.items?.filter((item) => item?.part_detail?.salable);
 | 
				
			||||||
 | 
					  }, [props.items]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return stockOperationModal({
 | 
					  return stockOperationModal({
 | 
				
			||||||
    ...props,
 | 
					    ...props,
 | 
				
			||||||
 | 
					    items: items,
 | 
				
			||||||
    fieldGenerator: stockAssignFields,
 | 
					    fieldGenerator: stockAssignFields,
 | 
				
			||||||
    endpoint: ApiEndpoints.stock_assign,
 | 
					    endpoint: ApiEndpoints.stock_assign,
 | 
				
			||||||
    title: t`Assign Stock to Customer`
 | 
					    title: t`Assign Stock to Customer`,
 | 
				
			||||||
 | 
					    successMessage: t`Stock assigned to customer`
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1013,7 +1029,8 @@ export function useDeleteStockItem(props: StockOperationProps) {
 | 
				
			|||||||
    fieldGenerator: stockDeleteFields,
 | 
					    fieldGenerator: stockDeleteFields,
 | 
				
			||||||
    endpoint: ApiEndpoints.stock_item_list,
 | 
					    endpoint: ApiEndpoints.stock_item_list,
 | 
				
			||||||
    modalFunc: useDeleteApiFormModal,
 | 
					    modalFunc: useDeleteApiFormModal,
 | 
				
			||||||
    title: t`Delete Stock Items`
 | 
					    title: t`Delete Stock Items`,
 | 
				
			||||||
 | 
					    successMessage: t`Stock deleted`
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,6 +48,7 @@ import { UserRoles } from '../../enums/Roles';
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
  type StockOperationProps,
 | 
					  type StockOperationProps,
 | 
				
			||||||
  useAddStockItem,
 | 
					  useAddStockItem,
 | 
				
			||||||
 | 
					  useAssignStockItem,
 | 
				
			||||||
  useCountStockItem,
 | 
					  useCountStockItem,
 | 
				
			||||||
  useRemoveStockItem,
 | 
					  useRemoveStockItem,
 | 
				
			||||||
  useStockFields,
 | 
					  useStockFields,
 | 
				
			||||||
@@ -588,7 +589,7 @@ export default function StockDetail() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const stockActionProps: StockOperationProps = useMemo(() => {
 | 
					  const stockActionProps: StockOperationProps = useMemo(() => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      items: stockitem,
 | 
					      items: [stockitem],
 | 
				
			||||||
      model: ModelType.stockitem,
 | 
					      model: ModelType.stockitem,
 | 
				
			||||||
      refresh: refreshInstance,
 | 
					      refresh: refreshInstance,
 | 
				
			||||||
      filters: {
 | 
					      filters: {
 | 
				
			||||||
@@ -601,6 +602,7 @@ export default function StockDetail() {
 | 
				
			|||||||
  const addStockItem = useAddStockItem(stockActionProps);
 | 
					  const addStockItem = useAddStockItem(stockActionProps);
 | 
				
			||||||
  const removeStockItem = useRemoveStockItem(stockActionProps);
 | 
					  const removeStockItem = useRemoveStockItem(stockActionProps);
 | 
				
			||||||
  const transferStockItem = useTransferStockItem(stockActionProps);
 | 
					  const transferStockItem = useTransferStockItem(stockActionProps);
 | 
				
			||||||
 | 
					  const assignToCustomer = useAssignStockItem(stockActionProps);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const serializeStockFields = useStockItemSerializeFields({
 | 
					  const serializeStockFields = useStockItemSerializeFields({
 | 
				
			||||||
    partId: stockitem.part,
 | 
					    partId: stockitem.part,
 | 
				
			||||||
@@ -731,7 +733,7 @@ export default function StockDetail() {
 | 
				
			|||||||
          {
 | 
					          {
 | 
				
			||||||
            name: t`Return`,
 | 
					            name: t`Return`,
 | 
				
			||||||
            tooltip: t`Return from customer`,
 | 
					            tooltip: t`Return from customer`,
 | 
				
			||||||
            hidden: !stockitem.sales_order,
 | 
					            hidden: !stockitem.customer,
 | 
				
			||||||
            icon: (
 | 
					            icon: (
 | 
				
			||||||
              <InvenTreeIcon
 | 
					              <InvenTreeIcon
 | 
				
			||||||
                icon='return_orders'
 | 
					                icon='return_orders'
 | 
				
			||||||
@@ -741,6 +743,17 @@ export default function StockDetail() {
 | 
				
			|||||||
            onClick: () => {
 | 
					            onClick: () => {
 | 
				
			||||||
              stockitem.pk && returnStockItem.open();
 | 
					              stockitem.pk && returnStockItem.open();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            name: t`Assign to Customer`,
 | 
				
			||||||
 | 
					            tooltip: t`Assign to a customer`,
 | 
				
			||||||
 | 
					            hidden: !!stockitem.customer,
 | 
				
			||||||
 | 
					            icon: (
 | 
				
			||||||
 | 
					              <InvenTreeIcon icon='customer' iconProps={{ color: 'blue' }} />
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            onClick: () => {
 | 
				
			||||||
 | 
					              stockitem.pk && assignToCustomer.open();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        ]}
 | 
					        ]}
 | 
				
			||||||
      />,
 | 
					      />,
 | 
				
			||||||
@@ -874,6 +887,7 @@ export default function StockDetail() {
 | 
				
			|||||||
        {transferStockItem.modal}
 | 
					        {transferStockItem.modal}
 | 
				
			||||||
        {serializeStockItem.modal}
 | 
					        {serializeStockItem.modal}
 | 
				
			||||||
        {returnStockItem.modal}
 | 
					        {returnStockItem.modal}
 | 
				
			||||||
 | 
					        {assignToCustomer.modal}
 | 
				
			||||||
      </Stack>
 | 
					      </Stack>
 | 
				
			||||||
    </InstanceDetail>
 | 
					    </InstanceDetail>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -604,7 +604,7 @@ export function StockItemTable({
 | 
				
			|||||||
          {
 | 
					          {
 | 
				
			||||||
            name: t`Assign to customer`,
 | 
					            name: t`Assign to customer`,
 | 
				
			||||||
            icon: <InvenTreeIcon icon='customer' />,
 | 
					            icon: <InvenTreeIcon icon='customer' />,
 | 
				
			||||||
            tooltip: t`Order new stock`,
 | 
					            tooltip: t`Assign items to a customer`,
 | 
				
			||||||
            disabled: !can_add_stock,
 | 
					            disabled: !can_add_stock,
 | 
				
			||||||
            onClick: () => {
 | 
					            onClick: () => {
 | 
				
			||||||
              assignStock.open();
 | 
					              assignStock.open();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -189,7 +189,7 @@ test('Stock - Stock Actions', async ({ page }) => {
 | 
				
			|||||||
  await page.getByRole('button', { name: 'Cancel' }).click();
 | 
					  await page.getByRole('button', { name: 'Cancel' }).click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Find an item which has been sent to a customer
 | 
					  // Find an item which has been sent to a customer
 | 
				
			||||||
  await page.goto(`${baseUrl}/stock/item/1012/details`);
 | 
					  await page.goto(`${baseUrl}/stock/item/1014/details`);
 | 
				
			||||||
  await page.getByText('Batch Code: 2022-11-12').waitFor();
 | 
					  await page.getByText('Batch Code: 2022-11-12').waitFor();
 | 
				
			||||||
  await page.getByText('Unavailable').waitFor();
 | 
					  await page.getByText('Unavailable').waitFor();
 | 
				
			||||||
  await page.getByLabel('action-menu-stock-operations').click();
 | 
					  await page.getByLabel('action-menu-stock-operations').click();
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user