mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	Import update (#10188)
* Add field to "update" existing records * Ensure the ID is first * Prevent editing of "ID" field * Extract db instance * Bump API version * Prevent edit of "id" field * Refactoring * Enhanced playwright tests for data importing * Update docs * Update src/backend/InvenTree/importer/models.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/frontend/src/forms/ImporterForms.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix silly AI mistake * Fix for table pagination - Ensure page does not exceed available records * Bug fix for playwright test * Add end-to-end API testing * Fix unit tests * Adjust table page logic * Ensure sensible page size * Simplify playwright test * Simplify test again * Tweak unit test - Importing has invalidated the BOM? * Adjust playwright tests * Further playwright fixes --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										9
									
								
								src/frontend/tests/fixtures/bom_data.csv
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								src/frontend/tests/fixtures/bom_data.csv
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,4 @@ | ||||
| Assembly,Component,Reference,Quantity,Overage,Allow Variants,Gets inherited,Optional,Consumable,Note,ID,Pricing min,Pricing max,Pricing min total,Pricing max total,Pricing updated,Component.Ipn,Component.Name,Component.Description,Validated,Available Stock,Available substitute stock,Available variant stock,External stock,On Order,In Production,Can Build | ||||
| 87,66,Screws,4.0,,False,False,False,False,,16,0.28,0.648622,1.12,2.594488,2024-08-08 06:55,,M3x8 Torx,"Torx head screw, M3 thread, 8.0mm",True,485.0,0.0,0.0,0.0,0.0,0.0,121.25 | ||||
| 87,67,Large screw,1.0,,False,False,False,False,,17,0.574802,0.574802,0.574802,0.574802,2024-07-27 05:13,,M3x10 Torx,"Torx head screw, M3 thread, 10.0mm",True,1450.0,0.0,0.0,0.0,0.0,0.0,1450.0 | ||||
| 87,82,Enclosure,1.0,,False,False,False,False,,15,,,,,2024-07-27 05:08,,1551ABK,"Small plastic enclosure, black",True,165.0,223.0,0.0,0.0,0.0,0.0,388.0 | ||||
| 87,88,PCBA,1.0,,True,False,False,False,Assembled board,23,80.431083,129.328176,80.431083,129.328176,2024-12-27 23:14,002.01-PCBA,Widget Board (assembled),Assembled PCB for converting electricity into magic smoke,True,55.0,0.0,0.0,0.0,0.0,0.0,55.0 | ||||
| Assembly,Component,Reference,Quantity,Allow Variants,Gets inherited,Optional,Consumable,Setup quantity,Attrition,Rounding multiple,Note,ID,Pricing min,Pricing max,Pricing min total,Pricing max total,Pricing updated,Component.Ipn,Component.Name,Component.Description,Validated,Available Stock,Available substitute stock,Available variant stock,External stock,On Order,In Production,Can Build | ||||
| 106,98,screws,5,FALSE,TRUE,FALSE,TRUE,0,0,0,,39,0.075,0.1,0.375,0.5,23/07/2025 9:12,,Wood Screw,Screw for fixing wood to other wood,TRUE,1604,0,0,0,0,0,320.8 | ||||
| 106,95,legs,4,FALSE,TRUE,FALSE,FALSE,0,0,0,,40,10.6,12.75,42.4,51,23/07/2025 9:12,,Leg,Leg for a chair or a table,TRUE,317,0,0,0,0,0,79.25 | ||||
| 109,92,paint,0.125,FALSE,FALSE,FALSE,FALSE,0,0,0,,43,1.403886,14.389836,0.175486,1.79873,23/07/2025 9:12,,Green Paint,Green Paint,TRUE,110.125,0,0,0,0,0,881 | ||||
|   | ||||
| 
 | 
| @@ -92,8 +92,6 @@ test('Parts - BOM', async ({ browser }) => { | ||||
|   await setTableChoiceFilter(page, 'active', 'Yes'); | ||||
|   await setTableChoiceFilter(page, 'BOM Valid', 'Yes'); | ||||
|  | ||||
|   await page.getByText('1 - 12 / 12').waitFor(); | ||||
|  | ||||
|   // Navigate to BOM for a particular assembly | ||||
|   await navigate(page, 'part/87/bom'); | ||||
|   await loadTab(page, 'Bill of Materials'); | ||||
| @@ -620,8 +618,11 @@ test('Parts - Bulk Edit', async ({ browser }) => { | ||||
|   await page.getByLabel('Select record 2', { exact: true }).click(); | ||||
|   await page.getByLabel('action-menu-part-actions').click(); | ||||
|   await page.getByLabel('action-menu-part-actions-set-category').click(); | ||||
|  | ||||
|   await page.getByLabel('related-field-category').fill('rnitu'); | ||||
|   await page.getByRole('option', { name: '- Furniture/Chairs' }).click; | ||||
|   await page.waitForTimeout(250); | ||||
|  | ||||
|   await page.getByRole('option', { name: '- Furniture/Chairs' }).click(); | ||||
|   await page.getByRole('button', { name: 'Update' }).click(); | ||||
|   await page.getByText('Items Updated').waitFor(); | ||||
| }); | ||||
|   | ||||
| @@ -14,6 +14,14 @@ test('Importing - Admin Center', async ({ browser }) => { | ||||
|  | ||||
|   const fileInput = await page.locator('input[type="file"]'); | ||||
|   await fileInput.setInputFiles('./tests/fixtures/bom_data.csv'); | ||||
|  | ||||
|   await page | ||||
|     .locator('label') | ||||
|     .filter({ hasText: 'Update Existing RecordsIf' }) | ||||
|     .locator('div') | ||||
|     .first() | ||||
|     .click(); | ||||
|  | ||||
|   await page.getByRole('button', { name: 'Submit' }).click(); | ||||
|  | ||||
|   // Submitting without selecting model type, should show error | ||||
| @@ -22,21 +30,54 @@ test('Importing - Admin Center', async ({ browser }) => { | ||||
|  | ||||
|   await page | ||||
|     .getByRole('textbox', { name: 'choice-field-model_type' }) | ||||
|     .fill('Cat'); | ||||
|   await page | ||||
|     .getByRole('option', { name: 'Part Category', exact: true }) | ||||
|     .click(); | ||||
|     .fill('bom'); | ||||
|   await page.getByRole('option', { name: 'BOM Item', exact: true }).click(); | ||||
|   await page.getByRole('button', { name: 'Submit' }).click(); | ||||
|  | ||||
|   await page.getByText('Description (optional)').waitFor(); | ||||
|   await page.getByText('Parent Category').waitFor(); | ||||
|   await page.getByText('Select the parent assembly').waitFor(); | ||||
|   await page.getByText('Select the component part').waitFor(); | ||||
|   await page.getByText('Existing database identifier for the record').waitFor(); | ||||
|  | ||||
|   await page | ||||
|     .getByRole('textbox', { name: 'import-column-map-reference' }) | ||||
|     .click(); | ||||
|   await page.getByRole('option', { name: 'Ignore this field' }).click(); | ||||
|  | ||||
|   await page.getByRole('button', { name: 'Accept Column Mapping' }).click(); | ||||
|  | ||||
|   // Check for expected ID values | ||||
|   for (const itemId of ['16', '17', '15', '23']) { | ||||
|     await page.getByRole('cell', { name: itemId, exact: true }); | ||||
|   } | ||||
|  | ||||
|   // Import all the records | ||||
|   await page | ||||
|     .getByRole('row', { name: 'Select all records Row Not' }) | ||||
|     .getByLabel('Select all records') | ||||
|     .click(); | ||||
|   await page | ||||
|     .getByRole('button', { name: 'action-button-import-selected' }) | ||||
|     .click(); | ||||
|  | ||||
|   await page.getByText('Data has been imported successfully').waitFor(); | ||||
|   await page.getByRole('button', { name: 'Close' }).click(); | ||||
|  | ||||
|   // Confirmation of full import success | ||||
|   await page.getByRole('cell', { name: '3 / 3' }).first().waitFor(); | ||||
|  | ||||
|   // Manually delete records | ||||
|   await page.getByRole('checkbox', { name: 'Select all records' }).click(); | ||||
|   await page | ||||
|     .getByRole('button', { name: 'action-button-delete-selected' }) | ||||
|     .click(); | ||||
|   await page.getByRole('button', { name: 'Delete', exact: true }).click(); | ||||
| }); | ||||
|  | ||||
| test('Importing - BOM', async ({ browser }) => { | ||||
|   const page = await doCachedLogin(browser, { | ||||
|     username: 'steven', | ||||
|     password: 'wizardstaff', | ||||
|     url: 'part/87/bom' | ||||
|     url: 'part/109/bom' | ||||
|   }); | ||||
|  | ||||
|   await page | ||||
| @@ -53,10 +94,10 @@ test('Importing - BOM', async ({ browser }) => { | ||||
|   await page.waitForTimeout(500); | ||||
|  | ||||
|   await page.getByText('Importing Data').waitFor(); | ||||
|   await page.getByText('0 / 4').waitFor(); | ||||
|   await page.getByText('0 / 3').waitFor(); | ||||
|  | ||||
|   await page.getByText('Torx head screw, M3 thread, 10.0mm').first().waitFor(); | ||||
|   await page.getByText('Small plastic enclosure, black').first().waitFor(); | ||||
|   await page.getByText('Screw for fixing wood').first().waitFor(); | ||||
|   await page.getByText('Leg for a chair or a table').first().waitFor(); | ||||
|  | ||||
|   // Select some rows | ||||
|   await page | ||||
| @@ -90,15 +131,16 @@ test('Importing - BOM', async ({ browser }) => { | ||||
|   await page.getByRole('button', { name: 'Submit' }).click(); | ||||
|   await page.waitForTimeout(250); | ||||
|  | ||||
|   await page.getByText('0 / 2', { exact: true }).waitFor(); | ||||
|   await page.getByText('0 / 1', { exact: true }).waitFor(); | ||||
|  | ||||
|   // Submit a row | ||||
|   await page | ||||
|     .getByRole('row', { name: 'Select record 1 2 Thumbnail' }) | ||||
|     .getByLabel('row-action-menu-') | ||||
|     .click(); | ||||
|  | ||||
|   await page.getByRole('menuitem', { name: 'Accept' }).click(); | ||||
|   await page.getByText('1 / 2', { exact: true }).waitFor(); | ||||
|   await page.getByText('0 / 1', { exact: true }).waitFor(); | ||||
| }); | ||||
|  | ||||
| test('Importing - Purchase Order', async ({ browser }) => { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user