mirror of
https://github.com/inventree/InvenTree.git
synced 2025-11-14 03:46:44 +00:00
Supplier Mixin (#9761)
* commit initial draft for supplier import * complete import wizard * allow importing only mp and sp * improved sample supplier plugin * add docs * add tests * bump api version * fix schema docu * fix issues from code review * commit unstaged changes * fix test * refactor part parameter bulk creation * try to fix test * fix tests * fix test for mysql * fix test * support multiple suppliers by a single plugin * hide import button if there is no supplier import plugin * make form submitable via enter * add pui test * try to prevent race condition * refactor api calls in pui tests * try to fix tests again? * fix tests * trigger: ci * update changelog * fix api_version * fix style * Update CHANGELOG.md Co-authored-by: Matthias Mair <code@mjmair.com> * add user docs --------- Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
10
src/frontend/tests/api.ts
Normal file
10
src/frontend/tests/api.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { request } from '@playwright/test';
|
||||
import { adminuser, apiUrl } from './defaults';
|
||||
|
||||
export const createApi = () =>
|
||||
request.newContext({
|
||||
baseURL: apiUrl,
|
||||
extraHTTPHeaders: {
|
||||
Authorization: `Basic ${btoa(`${adminuser.username}:${adminuser.password}`)}`
|
||||
}
|
||||
});
|
||||
@@ -1,3 +1,6 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { createApi } from './api';
|
||||
|
||||
/**
|
||||
* Open the filter drawer for the currently visible table
|
||||
* @param page - The page object
|
||||
@@ -130,3 +133,20 @@ export const globalSearch = async (page, query) => {
|
||||
await page.getByPlaceholder('Enter search text').fill(query);
|
||||
await page.waitForTimeout(300);
|
||||
};
|
||||
|
||||
export const deletePart = async (name: string) => {
|
||||
const api = await createApi();
|
||||
const parts = await api
|
||||
.get('part/', {
|
||||
params: { search: name }
|
||||
})
|
||||
.then((res) => res.json());
|
||||
const existingPart = parts.find((p: any) => p.name === name);
|
||||
if (existingPart) {
|
||||
await api.patch(`part/${existingPart.pk}/`, {
|
||||
data: { active: false }
|
||||
});
|
||||
const res = await api.delete(`part/${existingPart.pk}/`);
|
||||
expect(res.status()).toBe(204);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,10 +39,9 @@ test('Dashboard - Basic', async ({ browser }) => {
|
||||
await page.getByLabel('dashboard-accept-layout').click();
|
||||
});
|
||||
|
||||
test('Dashboard - Plugins', async ({ browser, request }) => {
|
||||
test('Dashboard - Plugins', async ({ browser }) => {
|
||||
// Ensure that the "SampleUI" plugin is enabled
|
||||
await setPluginState({
|
||||
request,
|
||||
plugin: 'sampleui',
|
||||
state: true
|
||||
});
|
||||
|
||||
@@ -2,12 +2,14 @@ import { test } from '../baseFixtures';
|
||||
import {
|
||||
clearTableFilters,
|
||||
clickOnRowMenu,
|
||||
deletePart,
|
||||
getRowFromCell,
|
||||
loadTab,
|
||||
navigate,
|
||||
setTableChoiceFilter
|
||||
} from '../helpers';
|
||||
import { doCachedLogin } from '../login';
|
||||
import { setPluginState, setSettingState } from '../settings';
|
||||
|
||||
/**
|
||||
* CHeck each panel tab for the "Parts" page
|
||||
@@ -645,3 +647,62 @@ test('Parts - Duplicate', async ({ browser }) => {
|
||||
await page.getByText('Copy Parameters', { exact: true }).waitFor();
|
||||
await page.getByText('Copy Tests', { exact: true }).waitFor();
|
||||
});
|
||||
|
||||
test('Parts - Import supplier part', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
url: 'part/category/1/parts'
|
||||
});
|
||||
|
||||
// Ensure that the sample supplier plugin is enabled
|
||||
await setPluginState({
|
||||
plugin: 'samplesupplier',
|
||||
state: true
|
||||
});
|
||||
|
||||
await setSettingState({
|
||||
setting: 'SUPPLIER',
|
||||
value: 3,
|
||||
type: 'plugin',
|
||||
plugin: 'samplesupplier'
|
||||
});
|
||||
|
||||
// cleanup old imported part if it exists
|
||||
await deletePart('BOLT-Steel-M5-5');
|
||||
await deletePart('BOLT-M5-5');
|
||||
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.getByRole('button', { name: 'action-button-import-part' }).click();
|
||||
await page
|
||||
.getByRole('textbox', { name: 'textbox-search-for-part' })
|
||||
.fill('M5');
|
||||
await page.waitForTimeout(250);
|
||||
await page
|
||||
.getByRole('textbox', { name: 'textbox-search-for-part' })
|
||||
.press('Enter');
|
||||
|
||||
await page.getByText('Bolt M5x5mm Steel').waitFor();
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-import-part-BOLT-Steel-M5-5' })
|
||||
.click();
|
||||
await page.waitForTimeout(250);
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-import-part-now' })
|
||||
.click();
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-import-create-parameters' })
|
||||
.dispatchEvent('click');
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-import-stock-next' })
|
||||
.dispatchEvent('click');
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-import-close' })
|
||||
.dispatchEvent('click');
|
||||
|
||||
// cleanup imported part if it exists
|
||||
await deletePart('BOLT-Steel-M5-5');
|
||||
await deletePart('BOLT-M5-5');
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ test('Machines - Admin Panel', async ({ browser }) => {
|
||||
await page.getByText('There are no machine registry errors').waitFor();
|
||||
});
|
||||
|
||||
test('Machines - Activation', async ({ browser, request }) => {
|
||||
test('Machines - Activation', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree',
|
||||
@@ -27,7 +27,6 @@ test('Machines - Activation', async ({ browser, request }) => {
|
||||
|
||||
// Ensure that the sample machine plugin is enabled
|
||||
await setPluginState({
|
||||
request,
|
||||
plugin: 'sample-printer-machine-plugin',
|
||||
state: true
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ import { doCachedLogin } from './login';
|
||||
* Test the "admin" account
|
||||
* - This is a superuser account, so should have *all* permissions available
|
||||
*/
|
||||
test('Permissions - Admin', async ({ browser, request }) => {
|
||||
test('Permissions - Admin', async ({ browser }) => {
|
||||
// Login, and start on the "admin" page
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
@@ -57,7 +57,7 @@ test('Permissions - Admin', async ({ browser, request }) => {
|
||||
* Test the "reader" account
|
||||
* - This account is read-only, but should be able to access *most* pages
|
||||
*/
|
||||
test('Permissions - Reader', async ({ browser, request }) => {
|
||||
test('Permissions - Reader', async ({ browser }) => {
|
||||
// Login, and start on the "admin" page
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'reader',
|
||||
|
||||
@@ -11,7 +11,7 @@ import { doCachedLogin } from './login.js';
|
||||
import { setPluginState, setSettingState } from './settings.js';
|
||||
|
||||
// Unit test for plugin settings
|
||||
test('Plugins - Settings', async ({ browser, request }) => {
|
||||
test('Plugins - Settings', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
@@ -19,7 +19,6 @@ test('Plugins - Settings', async ({ browser, request }) => {
|
||||
|
||||
// Ensure that the SampleIntegration plugin is enabled
|
||||
await setPluginState({
|
||||
request,
|
||||
plugin: 'sample',
|
||||
state: true
|
||||
});
|
||||
@@ -63,12 +62,11 @@ test('Plugins - Settings', async ({ browser, request }) => {
|
||||
await page.getByText('Mouser Electronics').click();
|
||||
});
|
||||
|
||||
test('Plugins - User Settings', async ({ browser, request }) => {
|
||||
test('Plugins - User Settings', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
// Ensure that the SampleIntegration plugin is enabled
|
||||
await setPluginState({
|
||||
request,
|
||||
plugin: 'sample',
|
||||
state: true
|
||||
});
|
||||
@@ -149,7 +147,7 @@ test('Plugins - Functionality', async ({ browser }) => {
|
||||
.waitFor();
|
||||
});
|
||||
|
||||
test('Plugins - Panels', async ({ browser, request }) => {
|
||||
test('Plugins - Panels', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
@@ -157,14 +155,12 @@ test('Plugins - Panels', async ({ browser, request }) => {
|
||||
|
||||
// Ensure that UI plugins are enabled
|
||||
await setSettingState({
|
||||
request,
|
||||
setting: 'ENABLE_PLUGINS_INTERFACE',
|
||||
value: true
|
||||
});
|
||||
|
||||
// Ensure that the SampleUI plugin is enabled
|
||||
await setPluginState({
|
||||
request,
|
||||
plugin: 'sampleui',
|
||||
state: true
|
||||
});
|
||||
@@ -192,7 +188,6 @@ test('Plugins - Panels', async ({ browser, request }) => {
|
||||
|
||||
// Disable the plugin, and ensure it is no longer visible
|
||||
await setPluginState({
|
||||
request,
|
||||
plugin: 'sampleui',
|
||||
state: false
|
||||
});
|
||||
@@ -201,7 +196,7 @@ test('Plugins - Panels', async ({ browser, request }) => {
|
||||
/**
|
||||
* Unit test for custom admin integration for plugins
|
||||
*/
|
||||
test('Plugins - Custom Admin', async ({ browser, request }) => {
|
||||
test('Plugins - Custom Admin', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
@@ -209,7 +204,6 @@ test('Plugins - Custom Admin', async ({ browser, request }) => {
|
||||
|
||||
// Ensure that the SampleUI plugin is enabled
|
||||
await setPluginState({
|
||||
request,
|
||||
plugin: 'sampleui',
|
||||
state: true
|
||||
});
|
||||
@@ -235,7 +229,7 @@ test('Plugins - Custom Admin', async ({ browser, request }) => {
|
||||
await page.getByText('hello: world').waitFor();
|
||||
});
|
||||
|
||||
test('Plugins - Locate Item', async ({ browser, request }) => {
|
||||
test('Plugins - Locate Item', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
@@ -243,7 +237,6 @@ test('Plugins - Locate Item', async ({ browser, request }) => {
|
||||
|
||||
// Ensure that the sample location plugin is enabled
|
||||
await setPluginState({
|
||||
request,
|
||||
plugin: 'samplelocate',
|
||||
state: true
|
||||
});
|
||||
|
||||
@@ -77,7 +77,7 @@ test('Printing - Report Printing', async ({ browser }) => {
|
||||
await page.context().close();
|
||||
});
|
||||
|
||||
test('Printing - Report Editing', async ({ browser, request }) => {
|
||||
test('Printing - Report Editing', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
@@ -85,7 +85,6 @@ test('Printing - Report Editing', async ({ browser, request }) => {
|
||||
|
||||
// activate the sample plugin for this test
|
||||
await setPluginState({
|
||||
request,
|
||||
plugin: 'sampleui',
|
||||
state: true
|
||||
});
|
||||
@@ -140,7 +139,6 @@ test('Printing - Report Editing', async ({ browser, request }) => {
|
||||
|
||||
// deactivate the sample plugin again after the test
|
||||
await setPluginState({
|
||||
request,
|
||||
plugin: 'sampleui',
|
||||
state: false
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createApi } from './api.js';
|
||||
import { expect, test } from './baseFixtures.js';
|
||||
import { apiUrl } from './defaults.js';
|
||||
import { getRowFromCell, loadTab, navigate } from './helpers.js';
|
||||
import { doCachedLogin } from './login.js';
|
||||
import { setPluginState, setSettingState } from './settings.js';
|
||||
@@ -134,7 +134,7 @@ test('Settings - User', async ({ browser }) => {
|
||||
.waitFor();
|
||||
});
|
||||
|
||||
test('Settings - Global', async ({ browser, request }) => {
|
||||
test('Settings - Global', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'steven',
|
||||
password: 'wizardstaff',
|
||||
@@ -144,7 +144,6 @@ test('Settings - Global', async ({ browser, request }) => {
|
||||
// Ensure the "slack" notification plugin is enabled
|
||||
// This is to ensure it is visible in the "notification" settings tab
|
||||
await setPluginState({
|
||||
request,
|
||||
plugin: 'inventree-slack-notification',
|
||||
state: true
|
||||
});
|
||||
@@ -312,7 +311,7 @@ test('Settings - Admin', async ({ browser }) => {
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
});
|
||||
|
||||
test('Settings - Admin - Barcode History', async ({ browser, request }) => {
|
||||
test('Settings - Admin - Barcode History', async ({ browser }) => {
|
||||
// Login with admin credentials
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
@@ -321,25 +320,21 @@ test('Settings - Admin - Barcode History', async ({ browser, request }) => {
|
||||
|
||||
// Ensure that the "save scans" setting is enabled
|
||||
await setSettingState({
|
||||
request: request,
|
||||
setting: 'BARCODE_STORE_RESULTS',
|
||||
value: true
|
||||
});
|
||||
|
||||
// Scan some barcodes (via API calls)
|
||||
const barcodes = ['ABC1234', 'XYZ5678', 'QRS9012'];
|
||||
const api = await createApi();
|
||||
|
||||
for (let i = 0; i < barcodes.length; i++) {
|
||||
const barcode = barcodes[i];
|
||||
const url = new URL('barcode/', apiUrl).toString();
|
||||
await request.post(url, {
|
||||
await api.post('barcode/', {
|
||||
data: {
|
||||
barcode: barcode
|
||||
},
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
Authorization: `Basic ${btoa('admin:inventree')}`
|
||||
}
|
||||
timeout: 5000
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,54 +1,48 @@
|
||||
import { expect } from 'playwright/test';
|
||||
|
||||
import { apiUrl } from './defaults';
|
||||
import { createApi } from './api';
|
||||
|
||||
/*
|
||||
* Set the value of a global setting in the database
|
||||
*/
|
||||
export const setSettingState = async ({
|
||||
request,
|
||||
setting,
|
||||
value
|
||||
value,
|
||||
type = 'global',
|
||||
plugin
|
||||
}: {
|
||||
request: any;
|
||||
setting: string;
|
||||
value: any;
|
||||
type?: 'global' | 'plugin';
|
||||
plugin?: string;
|
||||
}) => {
|
||||
const url = new URL(`settings/global/${setting}/`, apiUrl).toString();
|
||||
|
||||
const response = await request.patch(url, {
|
||||
const api = await createApi();
|
||||
const url =
|
||||
type === 'global'
|
||||
? `settings/global/${setting}/`
|
||||
: `plugins/${plugin}/settings/${setting}/`;
|
||||
const response = await api.patch(url, {
|
||||
data: {
|
||||
value: value
|
||||
},
|
||||
headers: {
|
||||
// Basic username: password authorization
|
||||
Authorization: `Basic ${btoa('admin:inventree')}`
|
||||
}
|
||||
});
|
||||
|
||||
expect(await response.status()).toBe(200);
|
||||
expect(response.status()).toBe(200);
|
||||
};
|
||||
|
||||
export const setPluginState = async ({
|
||||
request,
|
||||
plugin,
|
||||
state
|
||||
}: {
|
||||
request: any;
|
||||
plugin: string;
|
||||
state: boolean;
|
||||
}) => {
|
||||
const url = new URL(`plugins/${plugin}/activate/`, apiUrl).toString();
|
||||
|
||||
const response = await request.patch(url, {
|
||||
const api = await createApi();
|
||||
const response = await api.patch(`plugins/${plugin}/activate/`, {
|
||||
data: {
|
||||
active: state
|
||||
},
|
||||
headers: {
|
||||
// Basic username: password authorization
|
||||
Authorization: `Basic ${btoa('admin:inventree')}`
|
||||
}
|
||||
});
|
||||
|
||||
expect(await response.status()).toBe(200);
|
||||
expect(response.status()).toBe(200);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user