mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	[Refactor] Custom states (#8438)
* Enhancements for "custom state" form
- More intuitive form actions
* Improve back-end validation
* Improve table rendering
* Fix lookup for useStatusCodes
* Fix status display for SockDetail page
* Fix SalesOrder status display
* Refactor get_custom_classes
- Add StatusCode.custom_values method
* Fix for status table filters
* Cleanup (and note to self)
* Include custom state values in specific API endpoints
* Add serializer class definition
* Use same serializer for AllStatusView
* Fix API to match existing frontend type StatusCodeListInterface
* Enable filtering by reference status type
* Add option to duplicate an existing custom state
* Improved validation for the InvenTreeCustomUserStateModel class
* Code cleanup
* Fix default value in StockOperationsRow
* Use custom status values in stock operations
* Allow custom values
* Fix migration
* Bump API version
* Fix filtering of stock items by "status"
* Enhance status filter for orders
* Fix status code rendering
* Build Order API filter
* Update playwright tests for build filters
* Additional playwright tests for stock table filters
* Add 'custom' attribute
* Fix unit tests
* Add custom state field validation
* Implement StatusCodeMixin for setting status code values
* Clear out 'custom key' if the base key does not match
* Updated playwright testing
* Remove timeout
* Refactor detail pages which display status
* Update old migrations - add field validator
* Remove dead code
* Simplify API query filtering
* Revert "Simplify API query filtering"
This reverts commit 06c858ae7c.
* Fix save method
* Unit test fixes
* Fix for ReturnOrderLineItem
* Reorganize code
* Adjust unit test
			
			
This commit is contained in:
		| @@ -34,7 +34,7 @@ export const clickButtonIfVisible = async (page, name, timeout = 500) => { | ||||
| export const clearTableFilters = async (page) => { | ||||
|   await openFilterDrawer(page); | ||||
|   await clickButtonIfVisible(page, 'Clear Filters'); | ||||
|   await page.getByLabel('filter-drawer-close').click(); | ||||
|   await closeFilterDrawer(page); | ||||
| }; | ||||
|  | ||||
| export const setTableChoiceFilter = async (page, filter, value) => { | ||||
| @@ -42,7 +42,9 @@ export const setTableChoiceFilter = async (page, filter, value) => { | ||||
|  | ||||
|   await page.getByRole('button', { name: 'Add Filter' }).click(); | ||||
|   await page.getByPlaceholder('Select filter').fill(filter); | ||||
|   await page.getByRole('option', { name: 'Status' }).click(); | ||||
|   await page.getByPlaceholder('Select filter').click(); | ||||
|   await page.getByRole('option', { name: filter }).click(); | ||||
|  | ||||
|   await page.getByPlaceholder('Select filter value').click(); | ||||
|   await page.getByRole('option', { name: value }).click(); | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import { test } from '../baseFixtures.ts'; | ||||
| import { baseUrl } from '../defaults.ts'; | ||||
| import { | ||||
|   clickButtonIfVisible, | ||||
|   clearTableFilters, | ||||
|   getRowFromCell, | ||||
|   openFilterDrawer | ||||
|   setTableChoiceFilter | ||||
| } from '../helpers.ts'; | ||||
| import { doQuickLogin } from '../login.ts'; | ||||
|  | ||||
| @@ -266,6 +266,24 @@ test('Build Order - Filters', async ({ page }) => { | ||||
|  | ||||
|   await page.goto(`${baseUrl}/manufacturing/index/buildorders`); | ||||
|  | ||||
|   await openFilterDrawer(page); | ||||
|   await clickButtonIfVisible(page, 'Clear Filters'); | ||||
|   await clearTableFilters(page); | ||||
|   await page.getByText('1 - 24 / 24').waitFor(); | ||||
|  | ||||
|   // Toggle 'Outstanding' filter | ||||
|   await setTableChoiceFilter(page, 'Outstanding', 'Yes'); | ||||
|   await page.getByText('1 - 18 / 18').waitFor(); | ||||
|   await clearTableFilters(page); | ||||
|   await setTableChoiceFilter(page, 'Outstanding', 'No'); | ||||
|   await page.getByText('1 - 6 / 6').waitFor(); | ||||
|   await clearTableFilters(page); | ||||
|  | ||||
|   // Filter by custom status code | ||||
|   await setTableChoiceFilter(page, 'Status', 'Pending Approval'); | ||||
|  | ||||
|   // Single result - navigate through to the build order | ||||
|   await page.getByText('1 - 1 / 1').waitFor(); | ||||
|   await page.getByRole('cell', { name: 'BO0023' }).click(); | ||||
|  | ||||
|   await page.getByText('On Hold').first().waitFor(); | ||||
|   await page.getByText('Pending Approval').first().waitFor(); | ||||
| }); | ||||
|   | ||||
| @@ -1,6 +1,11 @@ | ||||
| import { test } from '../baseFixtures.js'; | ||||
| import { baseUrl } from '../defaults.js'; | ||||
| import { clickButtonIfVisible, openFilterDrawer } from '../helpers.js'; | ||||
| import { | ||||
|   clearTableFilters, | ||||
|   clickButtonIfVisible, | ||||
|   openFilterDrawer, | ||||
|   setTableChoiceFilter | ||||
| } from '../helpers.js'; | ||||
| import { doQuickLogin } from '../login.js'; | ||||
|  | ||||
| test('Stock - Basic Tests', async ({ page }) => { | ||||
| @@ -84,9 +89,15 @@ test('Stock - Filters', async ({ page }) => { | ||||
|     .getByRole('cell', { name: 'A round table - with blue paint' }) | ||||
|     .waitFor(); | ||||
|  | ||||
|   // Clear filters (ready for next set of tests) | ||||
|   await openFilterDrawer(page); | ||||
|   await clickButtonIfVisible(page, 'Clear Filters'); | ||||
|   // Filter by custom status code | ||||
|   await clearTableFilters(page); | ||||
|   await setTableChoiceFilter(page, 'Status', 'Incoming goods inspection'); | ||||
|   await page.getByText('1 - 8 / 8').waitFor(); | ||||
|   await page.getByRole('cell', { name: '1551AGY' }).first().waitFor(); | ||||
|   await page.getByRole('cell', { name: 'widget.blue' }).first().waitFor(); | ||||
|   await page.getByRole('cell', { name: '002.01-PCBA' }).first().waitFor(); | ||||
|  | ||||
|   await clearTableFilters(page); | ||||
| }); | ||||
|  | ||||
| test('Stock - Serial Numbers', async ({ page }) => { | ||||
| @@ -158,47 +169,58 @@ test('Stock - Serial Numbers', async ({ page }) => { | ||||
| test('Stock - Stock Actions', async ({ page }) => { | ||||
|   await doQuickLogin(page); | ||||
|  | ||||
|   // Find an in-stock, untracked item | ||||
|   await page.goto( | ||||
|     `${baseUrl}/stock/location/index/stock-items?in_stock=1&serialized=0` | ||||
|   ); | ||||
|   await page.getByText('530470210').first().click(); | ||||
|   await page.goto(`${baseUrl}/stock/item/1225/details`); | ||||
|  | ||||
|   // Helper function to launch a stock action | ||||
|   const launchStockAction = async (action: string) => { | ||||
|     await page.getByLabel('action-menu-stock-operations').click(); | ||||
|     await page.getByLabel(`action-menu-stock-operations-${action}`).click(); | ||||
|   }; | ||||
|  | ||||
|   const setStockStatus = async (status: string) => { | ||||
|     await page.getByLabel('action-button-change-status').click(); | ||||
|     await page.getByLabel('choice-field-status').click(); | ||||
|     await page.getByRole('option', { name: status }).click(); | ||||
|   }; | ||||
|  | ||||
|   // Check for required values | ||||
|   await page.getByText('Status', { exact: true }).waitFor(); | ||||
|   await page.getByText('Custom Status', { exact: true }).waitFor(); | ||||
|   await page.getByText('Attention needed').waitFor(); | ||||
|   await page | ||||
|     .locator('div') | ||||
|     .filter({ hasText: /^Quantity: 270$/ }) | ||||
|     .first() | ||||
|     .getByLabel('Stock Details') | ||||
|     .getByText('Incoming goods inspection') | ||||
|     .waitFor(); | ||||
|   await page.getByText('123').first().waitFor(); | ||||
|  | ||||
|   // Check for expected action sections | ||||
|   await page.getByLabel('action-menu-barcode-actions').click(); | ||||
|   await page.getByLabel('action-menu-barcode-actions-link-barcode').click(); | ||||
|   await page.getByRole('banner').getByRole('button').click(); | ||||
|  | ||||
|   await page.getByLabel('action-menu-printing-actions').click(); | ||||
|   await page.getByLabel('action-menu-printing-actions-print-labels').click(); | ||||
|   await page.getByRole('button', { name: 'Cancel' }).click(); | ||||
|  | ||||
|   await page.getByLabel('action-menu-stock-operations').click(); | ||||
|   await page.getByLabel('action-menu-stock-operations-count').waitFor(); | ||||
|   await page.getByLabel('action-menu-stock-operations-add').waitFor(); | ||||
|   await page.getByLabel('action-menu-stock-operations-remove').waitFor(); | ||||
|  | ||||
|   await page.getByLabel('action-menu-stock-operations-transfer').click(); | ||||
|   await page.getByLabel('text-field-notes').fill('test notes'); | ||||
|   // Add stock, and change status | ||||
|   await launchStockAction('add'); | ||||
|   await page.getByLabel('number-field-quantity').fill('12'); | ||||
|   await setStockStatus('Lost'); | ||||
|   await page.getByRole('button', { name: 'Submit' }).click(); | ||||
|   await page.getByText('This field is required.').first().waitFor(); | ||||
|  | ||||
|   // Set the status field | ||||
|   await page.getByLabel('action-button-change-status').click(); | ||||
|   await page.getByLabel('choice-field-status').click(); | ||||
|   await page.getByText('Attention needed').click(); | ||||
|   await page.getByText('Lost').first().waitFor(); | ||||
|   await page.getByText('Unavailable').first().waitFor(); | ||||
|   await page.getByText('135').first().waitFor(); | ||||
|  | ||||
|   // Set the packaging field | ||||
|   await page.getByLabel('action-button-adjust-packaging').click(); | ||||
|   await page.getByLabel('text-field-packaging').fill('test packaging'); | ||||
|   // Remove stock, and change status | ||||
|   await launchStockAction('remove'); | ||||
|   await page.getByLabel('number-field-quantity').fill('99'); | ||||
|   await setStockStatus('Damaged'); | ||||
|   await page.getByRole('button', { name: 'Submit' }).click(); | ||||
|  | ||||
|   // Close the dialog | ||||
|   await page.getByRole('button', { name: 'Cancel' }).click(); | ||||
|   await page.getByText('36').first().waitFor(); | ||||
|   await page.getByText('Damaged').first().waitFor(); | ||||
|  | ||||
|   // Count stock and change status (reverting to original value) | ||||
|   await launchStockAction('count'); | ||||
|   await page.getByLabel('number-field-quantity').fill('123'); | ||||
|   await setStockStatus('Incoming goods inspection'); | ||||
|   await page.getByRole('button', { name: 'Submit' }).click(); | ||||
|  | ||||
|   await page.getByText('123').first().waitFor(); | ||||
|   await page.getByText('Custom Status').first().waitFor(); | ||||
|   await page.getByText('Incoming goods inspection').first().waitFor(); | ||||
|  | ||||
|   // Find an item which has been sent to a customer | ||||
|   await page.goto(`${baseUrl}/stock/item/1014/details`); | ||||
| @@ -220,7 +242,4 @@ test('Stock - Tracking', async ({ page }) => { | ||||
|   await page.getByText('- - Factory/Office Block/Room').first().waitFor(); | ||||
|   await page.getByRole('link', { name: 'Widget Assembly' }).waitFor(); | ||||
|   await page.getByRole('cell', { name: 'Installed into assembly' }).waitFor(); | ||||
|  | ||||
|   await page.waitForTimeout(1500); | ||||
|   return; | ||||
| }); | ||||
|   | ||||
| @@ -153,7 +153,7 @@ test('Settings - Admin - Barcode History', async ({ page, request }) => { | ||||
|   await page.getByRole('menuitem', { name: 'Admin Center' }).click(); | ||||
|   await page.getByRole('tab', { name: 'Barcode Scans' }).click(); | ||||
|  | ||||
|   await page.waitForTimeout(2000); | ||||
|   await page.waitForTimeout(500); | ||||
|  | ||||
|   // Barcode history is displayed in table | ||||
|   barcodes.forEach(async (barcode) => { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user