mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 05:05:42 +00:00 
			
		
		
		
	Build order consume (#8191)
* Adds "consumed" field to BuildLine model * Expose new field to serializer * Add "consumed" column to BuildLineTable * Boolean column tweaks * Increase consumed count when completing allocation * Add comment * Update migration * Add serializer for consuming build items * Improve build-line sub-table * Refactor BuildItem.complete_allocation method - Allow optional quantity to be specified - Adjust the allocated quantity when consuming * Perform consumption * Add "BuildConsume" API endpoint * Implement frontend form * Fixes for serializer * Enhance front-end form * Fix rendering of BuildLineTable * Further improve rendering * Bump API version * Update API description * Add option to consume by specifying a list of BuildLine objects * Add form to consume stock via BuildLine reference * Fix api_version * Fix backup colors * Fix typo * Fix migrations * Fix build forms * Forms fixes * Fix formatting * Fixes for BuildLineTable * Account for consumed stock in requirements calculation * Reduce API requirements for BuildLineTable * Docs updates * Updated playwright testing * Update src/frontend/src/forms/BuildForms.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/frontend/src/tables/build/BuildLineTable.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add unit test for filters * Add functional tests * Tweak query count * Increase max query time for testing * adjust unit test again * Prevent consumption of "tracked" items * Adjust playwright tests * Fix table * Fix rendering --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
		| @@ -350,6 +350,59 @@ test('Build Order - Allocation', async ({ browser }) => { | ||||
|     .waitFor(); | ||||
| }); | ||||
|  | ||||
| // Test partial stock consumption against build order | ||||
| test('Build Order - Consume Stock', async ({ browser }) => { | ||||
|   const page = await doCachedLogin(browser, { | ||||
|     url: 'manufacturing/build-order/24/line-items' | ||||
|   }); | ||||
|  | ||||
|   // Check for expected progress values | ||||
|   await page.getByText('2 / 2', { exact: true }).waitFor(); | ||||
|   await page.getByText('8 / 10', { exact: true }).waitFor(); | ||||
|   await page.getByText('5 / 35', { exact: true }).waitFor(); | ||||
|   await page.getByText('5 / 40', { exact: true }).waitFor(); | ||||
|  | ||||
|   // Open the "Allocate Stock" dialog | ||||
|   await page.getByRole('checkbox', { name: 'Select all records' }).click(); | ||||
|   await page | ||||
|     .getByRole('button', { name: 'action-button-allocate-stock' }) | ||||
|     .click(); | ||||
|   await page | ||||
|     .getByLabel('Allocate Stock') | ||||
|     .getByText('5 / 35', { exact: true }) | ||||
|     .waitFor(); | ||||
|   await page.getByRole('button', { name: 'Cancel' }).click(); | ||||
|  | ||||
|   // Open the "Consume Stock" dialog | ||||
|   await page | ||||
|     .getByRole('button', { name: 'action-button-consume-stock' }) | ||||
|     .click(); | ||||
|   await page.getByLabel('Consume Stock').getByText('2 / 2').waitFor(); | ||||
|   await page.getByLabel('Consume Stock').getByText('8 / 10').waitFor(); | ||||
|   await page.getByLabel('Consume Stock').getByText('5 / 35').waitFor(); | ||||
|   await page.getByLabel('Consume Stock').getByText('5 / 40').waitFor(); | ||||
|   await page | ||||
|     .getByRole('textbox', { name: 'text-field-notes' }) | ||||
|     .fill('some notes here...'); | ||||
|   await page.getByRole('button', { name: 'Cancel' }).click(); | ||||
|  | ||||
|   // Try with a different build order | ||||
|   await navigate(page, 'manufacturing/build-order/26/line-items'); | ||||
|   await page.getByRole('checkbox', { name: 'Select all records' }).click(); | ||||
|   await page | ||||
|     .getByRole('button', { name: 'action-button-consume-stock' }) | ||||
|     .click(); | ||||
|  | ||||
|   await page.getByLabel('Consume Stock').getByText('306 / 1,900').waitFor(); | ||||
|   await page | ||||
|     .getByLabel('Consume Stock') | ||||
|     .getByText('Fully consumed') | ||||
|     .first() | ||||
|     .waitFor(); | ||||
|  | ||||
|   await page.waitForTimeout(1000); | ||||
| }); | ||||
|  | ||||
| test('Build Order - Tracked Outputs', async ({ browser }) => { | ||||
|   const page = await doCachedLogin(browser, { | ||||
|     url: 'manufacturing/build-order/10/incomplete-outputs' | ||||
|   | ||||
| @@ -249,7 +249,13 @@ test('Parts - Requirements', async ({ browser }) => { | ||||
|   await page.getByText('5 / 100').waitFor(); // Allocated to sales orders | ||||
|   await page.getByText('10 / 125').waitFor(); // In production | ||||
|  | ||||
|   await page.waitForTimeout(2500); | ||||
|   // Also check requirements for part with open build orders which have been partially consumed | ||||
|   await navigate(page, 'part/105/details'); | ||||
|  | ||||
|   await page.getByText('Required: 2').waitFor(); | ||||
|   await page.getByText('Available: 32').waitFor(); | ||||
|   await page.getByText('In Stock: 34').waitFor(); | ||||
|   await page.getByText('2 / 2').waitFor(); // Allocated to build orders | ||||
| }); | ||||
|  | ||||
| test('Parts - Allocations', async ({ browser }) => { | ||||
| @@ -377,7 +383,6 @@ test('Parts - Pricing (Supplier)', async ({ browser }) => { | ||||
|  | ||||
|   // Supplier Pricing | ||||
|   await page.getByRole('button', { name: 'Supplier Pricing' }).click(); | ||||
|   await page.waitForTimeout(500); | ||||
|   await page.getByRole('button', { name: 'SKU Not sorted' }).waitFor(); | ||||
|  | ||||
|   // Supplier Pricing - linkjumping | ||||
|   | ||||
| @@ -323,6 +323,7 @@ test('Stock - Return Items', async ({ browser }) => { | ||||
|       name: 'action-menu-stock-operations-return-stock' | ||||
|     }) | ||||
|     .click(); | ||||
|  | ||||
|   await page.getByText('#128').waitFor(); | ||||
|   await page.getByText('Merge into existing stock').waitFor(); | ||||
|   await page.getByRole('textbox', { name: 'number-field-quantity' }).fill('0'); | ||||
|   | ||||
| @@ -52,8 +52,6 @@ test('Plugins - Settings', async ({ browser, request }) => { | ||||
|     .fill(originalValue == '999' ? '1000' : '999'); | ||||
|   await page.getByRole('button', { name: 'Submit' }).click(); | ||||
|  | ||||
|   await page.waitForTimeout(500); | ||||
|  | ||||
|   // Change it back | ||||
|   await page.getByLabel('edit-setting-NUMERICAL_SETTING').click(); | ||||
|   await page.getByLabel('number-field-value').fill(originalValue); | ||||
| @@ -164,8 +162,6 @@ test('Plugins - Panels', async ({ browser, request }) => { | ||||
|     value: true | ||||
|   }); | ||||
|  | ||||
|   await page.waitForTimeout(500); | ||||
|  | ||||
|   // Ensure that the SampleUI plugin is enabled | ||||
|   await setPluginState({ | ||||
|     request, | ||||
| @@ -173,8 +169,6 @@ test('Plugins - Panels', async ({ browser, request }) => { | ||||
|     state: true | ||||
|   }); | ||||
|  | ||||
|   await page.waitForTimeout(500); | ||||
|  | ||||
|   // Navigate to the "part" page | ||||
|   await navigate(page, 'part/69/'); | ||||
|  | ||||
| @@ -186,20 +180,14 @@ test('Plugins - Panels', async ({ browser, request }) => { | ||||
|  | ||||
|   // Check out each of the plugin panels | ||||
|   await loadTab(page, 'Broken Panel'); | ||||
|   await page.waitForTimeout(500); | ||||
|  | ||||
|   await page.getByText('Error occurred while loading plugin content').waitFor(); | ||||
|  | ||||
|   await loadTab(page, 'Dynamic Panel'); | ||||
|   await page.waitForTimeout(500); | ||||
|  | ||||
|   await page.getByText('Instance ID: 69'); | ||||
|   await page | ||||
|     .getByText('This panel has been dynamically rendered by the plugin system') | ||||
|     .waitFor(); | ||||
|  | ||||
|   await loadTab(page, 'Part Panel'); | ||||
|   await page.waitForTimeout(500); | ||||
|   await page.getByText('This content has been rendered by a custom plugin'); | ||||
|  | ||||
|   // Disable the plugin, and ensure it is no longer visible | ||||
| @@ -260,8 +248,6 @@ test('Plugins - Locate Item', async ({ browser, request }) => { | ||||
|     state: true | ||||
|   }); | ||||
|  | ||||
|   await page.waitForTimeout(500); | ||||
|  | ||||
|   // Navigate to the "stock item" page | ||||
|   await navigate(page, 'stock/item/287/'); | ||||
|   await page.waitForLoadState('networkidle'); | ||||
| @@ -273,7 +259,6 @@ test('Plugins - Locate Item', async ({ browser, request }) => { | ||||
|  | ||||
|   // Show the location | ||||
|   await page.getByLabel('breadcrumb-1-factory').click(); | ||||
|   await page.waitForTimeout(500); | ||||
|  | ||||
|   await page.getByLabel('action-button-locate-item').click(); | ||||
|   await page.getByRole('button', { name: 'Submit' }).click(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user