2
0
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:
Oliver
2026-02-22 20:15:31 +11:00
committed by GitHub
parent cca35bb268
commit 2e22245255
10 changed files with 350 additions and 64 deletions

View File

@@ -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,

View File

@@ -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>
)
});

View File

@@ -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}

View File

@@ -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, {