2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-06-12 03:28:37 +00:00

Offload build output functions: (#11990)

* Offload build output functions:

- cancel output
- scrap output
- complete output

Perform these in the background worker, and monitor for progress on the frontend.

* Refactor "build cancel"

- Offload expensive ops to background worker

* Offload build complete task

* Remove @atomic decorator from functions

- Allows operations to be performed "incrementally"
- If one task times out, the next task will get the rest

* Bug fix

* Bump API version

* Fix isInTestMode check

* Handle case where task returns immediately

* Fix docstring

* fix test_api

* Tweak order of operations

* additional unit testing for further coverage

* Adjust unit tests

* Offload order completion tasks

* Remove bad code

* Updated playwright test

* Robustify playwright tests

* Bump number of allowed queries

* Revert "Remove bad code"

This reverts commit 3a3ac3bdc7.

* Revert "Offload order completion tasks"

This reverts commit 6066dabe43.
This commit is contained in:
Oliver
2026-05-24 09:26:43 +10:00
committed by GitHub
parent 7d61203be8
commit 749c4715ee
12 changed files with 739 additions and 196 deletions
+3 -3
View File
@@ -385,7 +385,7 @@ export function useCompleteBuildOutputsForm({
title: t`Complete Build Outputs`,
fields: buildOutputCompleteFields,
onFormSuccess: onFormSuccess,
successMessage: t`Build outputs have been completed`,
successMessage: null,
size: '80%'
});
}
@@ -466,7 +466,7 @@ export function useScrapBuildOutputsForm({
),
fields: buildOutputScrapFields,
onFormSuccess: onFormSuccess,
successMessage: t`Build outputs have been scrapped`,
successMessage: null,
size: '80%'
});
}
@@ -527,7 +527,7 @@ export function useCancelBuildOutputsForm({
),
fields: buildOutputCancelFields,
onFormSuccess: onFormSuccess,
successMessage: t`Build outputs have been cancelled`,
successMessage: null,
size: '80%'
});
}
@@ -346,31 +346,80 @@ export default function BuildOutputTable({
const [selectedOutputs, setSelectedOutputs] = useState<any[]>([]);
const [completeTaskId, setCompleteTaskId] = useState<string>('');
const [scrapTaskId, setScrapTaskId] = useState<string>('');
const [deleteTaskId, setDeleteTaskId] = useState<string>('');
useBackgroundTask({
taskId: completeTaskId,
message: t`Completing build outputs`,
successMessage: t`Build outputs have been completed`,
onSuccess: () => {
table.refreshTable(true);
refreshBuild();
}
});
useBackgroundTask({
taskId: scrapTaskId,
message: t`Scrapping build outputs`,
successMessage: t`Build outputs have been scrapped`,
onSuccess: () => {
table.refreshTable(true);
refreshBuild();
}
});
useBackgroundTask({
taskId: deleteTaskId,
message: t`Cancelling build outputs`,
successMessage: t`Build outputs have been cancelled`,
onSuccess: () => {
table.refreshTable(true);
refreshBuild();
}
});
const completeBuildOutputsForm = useCompleteBuildOutputsForm({
build: build,
outputs: selectedOutputs,
hasTrackedItems: hasTrackedItems,
onFormSuccess: () => {
table.refreshTable(true);
refreshBuild();
onFormSuccess: (response: any) => {
if (response.task_id) {
setCompleteTaskId(response.task_id);
} else {
// If no task ID is returned, immediately refresh the table and build data
table.refreshTable(true);
refreshBuild();
}
}
});
const scrapBuildOutputsForm = useScrapBuildOutputsForm({
build: build,
outputs: selectedOutputs,
onFormSuccess: () => {
table.refreshTable(true);
refreshBuild();
onFormSuccess: (response: any) => {
if (response.task_id) {
setScrapTaskId(response.task_id);
} else {
// If no task ID is returned, immediately refresh the table and build data
table.refreshTable(true);
refreshBuild();
}
}
});
const cancelBuildOutputsForm = useCancelBuildOutputsForm({
build: build,
outputs: selectedOutputs,
onFormSuccess: () => {
table.refreshTable(true);
refreshBuild();
onFormSuccess: (response: any) => {
if (response.task_id) {
setDeleteTaskId(response.task_id);
} else {
// If no task ID is returned, immediately refresh the table and build data
table.refreshTable(true);
refreshBuild();
}
}
});
+38 -1
View File
@@ -38,6 +38,7 @@ test('Build Order - Basic Tests', async ({ browser }) => {
await clearTableFilters(page);
// We have now loaded the "Build Order" table. Check for some expected texts
await page.getByPlaceholder('Search').fill('7');
await page.getByText('On Hold').first().waitFor();
await page.getByText('Pending').first().waitFor();
@@ -60,6 +61,7 @@ test('Build Order - Basic Tests', async ({ browser }) => {
await page.getByLabel('breadcrumb-0-manufacturing').click();
// Load a different build order
await page.getByPlaceholder('Search').fill('11');
await page.getByRole('cell', { name: 'BO0011' }).click();
// This build order should be "in production"
@@ -654,6 +656,7 @@ test('Build Order - Filters', async ({ browser }) => {
// Check for expected pagination text i.e. (1 - 24 / 24)
// Note: Due to other concurrent tests, the number of build orders may vary
await page.getByText(/1 - \d+ \/ \d+/).waitFor();
await page.getByPlaceholder('Search').fill('23');
await page.getByRole('cell', { name: 'BO0023' }).waitFor();
// Toggle 'Outstanding' filter
@@ -665,7 +668,7 @@ test('Build Order - Filters', async ({ browser }) => {
await page.getByRole('textbox', { name: 'table-search-input' }).fill('');
await setTableChoiceFilter(page, 'Outstanding', 'No');
await page.getByText('1 - 6 / 6').waitFor();
await page.getByText(/1 - \d+ \/ \d+/).waitFor();
await clearTableFilters(page);
@@ -699,6 +702,40 @@ test('Build Order - Duplicate', async ({ browser }) => {
await page.getByRole('tab', { name: 'Build Details' }).click();
await page.getByText('Pending').first().waitFor();
// Create a build output
await loadTab(page, 'Incomplete Outputs');
await page
.getByRole('button', { name: 'action-button-add-build-output' })
.click();
await page
.getByRole('textbox', { name: 'text-field-batch_code' })
.fill('BATCH-001');
await page.getByRole('button', { name: 'Submit' }).click();
// Cancel (delete) the build output
const cell = await page.getByRole('cell', { name: 'BATCH-001' }).first();
await clickOnRowMenu(cell);
await page.getByRole('menuitem', { name: 'Cancel' }).click();
await page.getByRole('button', { name: 'Submit' }).click();
// no more build outputs
await page.getByText('No records found').waitFor();
// Cancel the build
await page.getByRole('button', { name: 'action-menu-build-order-' }).click();
await page
.getByRole('menuitem', { name: 'action-menu-build-order-actions-cancel' })
.click();
await page
.getByRole('switch', { name: 'boolean-field-remove_allocated_stock' })
.click();
await page
.getByRole('switch', { name: 'boolean-field-remove_incomplete_outputs' })
.click();
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByText('Cancelled').first().waitFor();
});
// Tests for external build orders