mirror of
https://github.com/inventree/InvenTree.git
synced 2026-04-04 10:31:03 +00:00
Auto allocate tracked (#10887)
* Add "item_type" to BuildAutoAllocationSerializer * Update frontend to allow selection * Stub for allocating tracked items * Code for auto-allocating tracked outputs * Refactor auto-allocation code * UI updates * Bump API version * Auto refresh tracked items * Update CHANGELOG.md * docs entry * Add unit test * Add playwright testing
This commit is contained in:
@@ -226,6 +226,31 @@ export function useBuildOrderOutputFields({
|
||||
}, [quantity, batchGenerator.result, serialGenerator.result, trackable]);
|
||||
}
|
||||
|
||||
export function useBuildAutoAllocateFields({
|
||||
item_type
|
||||
}: {
|
||||
item_type: 'all' | 'tracked' | 'untracked';
|
||||
}): ApiFormFieldSet {
|
||||
return useMemo(() => {
|
||||
return {
|
||||
location: {},
|
||||
exclude_location: {},
|
||||
item_type: {
|
||||
value: item_type,
|
||||
hidden: true
|
||||
},
|
||||
interchangeable: {
|
||||
hidden: item_type === 'tracked'
|
||||
},
|
||||
substitutes: {},
|
||||
optional_items: {
|
||||
hidden: item_type === 'tracked',
|
||||
value: item_type === 'tracked' ? false : undefined
|
||||
}
|
||||
};
|
||||
}, [item_type]);
|
||||
}
|
||||
|
||||
function BuildOutputFormRow({
|
||||
props,
|
||||
record,
|
||||
|
||||
@@ -27,6 +27,7 @@ import type { RowAction, TableColumn } from '@lib/types/Tables';
|
||||
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
|
||||
import {
|
||||
useAllocateStockToBuildForm,
|
||||
useBuildAutoAllocateFields,
|
||||
useBuildOrderFields,
|
||||
useConsumeBuildLinesForm
|
||||
} from '../../forms/BuildForms';
|
||||
@@ -574,17 +575,9 @@ export default function BuildLineTable({
|
||||
url: ApiEndpoints.build_order_auto_allocate,
|
||||
pk: build.pk,
|
||||
title: t`Allocate Stock`,
|
||||
fields: {
|
||||
location: {
|
||||
filters: {
|
||||
structural: false
|
||||
}
|
||||
},
|
||||
exclude_location: {},
|
||||
interchangeable: {},
|
||||
substitutes: {},
|
||||
optional_items: {}
|
||||
},
|
||||
fields: useBuildAutoAllocateFields({
|
||||
item_type: 'untracked'
|
||||
}),
|
||||
initialData: {
|
||||
location: build.take_from,
|
||||
interchangeable: true,
|
||||
@@ -595,7 +588,7 @@ export default function BuildLineTable({
|
||||
table: table,
|
||||
preFormContent: (
|
||||
<Alert color='green' title={t`Auto Allocate Stock`}>
|
||||
<Text>{t`Automatically allocate stock to this build according to the selected options`}</Text>
|
||||
<Text>{t`Automatically allocate untracked BOM items to this build according to the selected options`}</Text>
|
||||
</Alert>
|
||||
)
|
||||
});
|
||||
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
IconBuildingFactory2,
|
||||
IconCircleCheck,
|
||||
IconCircleX,
|
||||
IconExclamationCircle
|
||||
IconExclamationCircle,
|
||||
IconWand
|
||||
} from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
@@ -33,6 +34,7 @@ import type { TableColumn } from '@lib/types/Tables';
|
||||
import { StylishText } from '../../components/items/StylishText';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import {
|
||||
useBuildAutoAllocateFields,
|
||||
useBuildOrderOutputFields,
|
||||
useCancelBuildOutputsForm,
|
||||
useCompleteBuildOutputsForm,
|
||||
@@ -213,6 +215,32 @@ export default function BuildOutputTable({
|
||||
}
|
||||
});
|
||||
|
||||
const autoAllocateStock = useCreateApiFormModal({
|
||||
url: ApiEndpoints.build_order_auto_allocate,
|
||||
pk: build.pk,
|
||||
title: t`Allocate Stock`,
|
||||
fields: useBuildAutoAllocateFields({
|
||||
item_type: 'tracked'
|
||||
}),
|
||||
initialData: {
|
||||
location: build.take_from,
|
||||
substitutes: true
|
||||
},
|
||||
successMessage: t`Auto-allocation in progress`,
|
||||
onFormSuccess: () => {
|
||||
// After a short delay, refresh the tracked items
|
||||
setTimeout(() => {
|
||||
refetchTrackedItems();
|
||||
}, 2500);
|
||||
},
|
||||
table: table,
|
||||
preFormContent: (
|
||||
<Alert color='green' title={t`Auto Allocate Stock`}>
|
||||
<Text>{t`Automatically allocate tracked BOM items to this build according to the selected options`}</Text>
|
||||
</Alert>
|
||||
)
|
||||
});
|
||||
|
||||
const hasTrackedItems: boolean = useMemo(() => {
|
||||
return (trackedItems?.length ?? 0) > 0;
|
||||
}, [trackedItems]);
|
||||
@@ -438,6 +466,16 @@ export default function BuildOutputTable({
|
||||
const tableActions = useMemo(() => {
|
||||
return [
|
||||
stockAdjustActions.dropdown,
|
||||
<ActionButton
|
||||
key='allocate-stock'
|
||||
icon={<IconWand />}
|
||||
color='blue'
|
||||
tooltip={t`Auto Allocate Stock`}
|
||||
hidden={!hasTrackedItems}
|
||||
onClick={() => {
|
||||
autoAllocateStock.open();
|
||||
}}
|
||||
/>,
|
||||
<ActionButton
|
||||
key='complete-selected-outputs'
|
||||
tooltip={t`Complete selected outputs`}
|
||||
@@ -480,6 +518,7 @@ export default function BuildOutputTable({
|
||||
];
|
||||
}, [
|
||||
build,
|
||||
hasTrackedItems,
|
||||
user,
|
||||
table.selectedRecords,
|
||||
table.hasSelectedRecords,
|
||||
@@ -680,6 +719,7 @@ export default function BuildOutputTable({
|
||||
return (
|
||||
<>
|
||||
{addBuildOutput.modal}
|
||||
{autoAllocateStock.modal}
|
||||
{completeBuildOutputsForm.modal}
|
||||
{scrapBuildOutputsForm.modal}
|
||||
{editBuildOutput.modal}
|
||||
|
||||
@@ -431,6 +431,40 @@ test('Build Order - Allocation', async ({ browser }) => {
|
||||
.waitFor();
|
||||
});
|
||||
|
||||
test('Build Order - Auto Allocate Tracked', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
url: 'manufacturing/build-order/27/consumed-stock'
|
||||
});
|
||||
|
||||
await loadTab(page, 'Incomplete Outputs');
|
||||
|
||||
await page.getByRole('cell', { name: '0 / 6' }).waitFor();
|
||||
|
||||
// Auto-allocate tracked stock
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-auto-allocate-' })
|
||||
.click();
|
||||
|
||||
// Wait for auto-filled form field
|
||||
await page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Factory$/ })
|
||||
.first()
|
||||
.waitFor();
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
// Wait for one of the required parts to be allocated
|
||||
await page.getByRole('cell', { name: '1 / 6' }).waitFor({ timeout: 7500 });
|
||||
|
||||
// Deallocate the item to return to the initial state
|
||||
const cell = await page.getByRole('cell', { name: '# 555' });
|
||||
await clickOnRowMenu(cell);
|
||||
await page.getByRole('menuitem', { name: 'Deallocate' }).click();
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
await page.getByRole('cell', { name: '0 / 6' }).waitFor({ timeout: 7500 });
|
||||
});
|
||||
|
||||
// Test partial stock consumption against build order
|
||||
test('Build Order - Consume Stock', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
|
||||
Reference in New Issue
Block a user