mirror of
https://github.com/inventree/InvenTree.git
synced 2026-05-28 11:59:23 +00:00
Transfer Order (#11281)
* initial skel commit for transfer orders * initial transfer order backend model * add some serializers, rename PLACED to ISSUED for TransferOrders * adding from admin console works * simple table list almost working, but we need to add order line items.... * add other cols to table * add Transfer Order from table view * moving towards a detail view * wip: adding detail view * add take from and destination serializer details * add other detail grid items * edit/duplicate transfer order * more action buttons * first crack at adding line items * add to line item * add filters * starting work on row actions * more action buttons for line items * fix copy lines in duplicate * basic allocation works * allocations table actions * allocate serials * allocated serial row expansion * add transferred qty to serializers * move items on complete, show in tracking * change panel to transferred stock upon complete * allow incomplete line items * disable edit allocations when completed * add ref pattern and to settings * add admin to line item inline * add calendar and parametric view * basic transfer order report * add transfer order ruleset * starting allocation buisness logic throughout for TOs * disable accept incomplete logic, which was incorrect, until I fix * fix incomplete allocation option * add transferred col to default report * add transfer order to calendar ics view * chain condition for readability * add transfer order allocations table to stockitem view * don't account TO allocations in availability * add transfer orders table for a part * 'consume' option by doing take_stock * squash migrations * starting to test transfer order * more transfer order tests * add transfer order consume test * wip, more tests * more transfer order tests * had to refresh_from_db * switch "to" to "transfer-order" in url paths * only select non-virtual parts from transfer order * add transfer order docs * deconflict migrations * fix frontend build error * fix validation on transfer order reference pattern * add oath2 scope for transfer order * fix state test to include transfer order state * add barcode_model_type_code for transfer order * bump api version * check view role for transfer order, remove debug/commented out lines * add serialized allocation test * Fix migrations * Frontend fixes * Implement required 'company' attribute * transfer order report context * attempt to fix tests * delete transfer order allocations on cancel * add a few playwright tests, more incoming * more playwright * add source and destination locations to table * deconflict migrations * Fix build issue * attempt to fix flaky transfer order test * duplicate transfer order before running tests * Adjust playwright tests * Fix migration dependency order --------- Co-authored-by: Oliver <oliver.henry.walters@gmail.com> Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import { expect, test } from '../baseFixtures.js';
|
||||
import { stevenuser } from '../defaults.js';
|
||||
import {
|
||||
activateCalendarView,
|
||||
clearTableFilters,
|
||||
clickButtonIfVisible,
|
||||
clickOnRowMenu,
|
||||
loadTab,
|
||||
navigate,
|
||||
openFilterDrawer,
|
||||
@@ -548,3 +550,209 @@ test('Stock - Location', async ({ browser }) => {
|
||||
await page.getByRole('button', { name: 'Scan', exact: true }).click();
|
||||
await page.getByText('No match found for barcode data').waitFor();
|
||||
});
|
||||
|
||||
test('Transfer Orders - General', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await page.getByRole('tab', { name: 'Stock' }).click();
|
||||
await page.waitForURL('**/stock/location/index/**');
|
||||
|
||||
await loadTab(page, 'Transfer Orders');
|
||||
|
||||
await clearTableFilters(page);
|
||||
|
||||
// We have now loaded the "Transfer Orders" table. Check for some expected texts
|
||||
await page.getByText('Complete').first().waitFor();
|
||||
await page.getByText('Issued').first().waitFor();
|
||||
await page.getByText('Cancelled').first().waitFor();
|
||||
|
||||
// Load a particular Transfer Order
|
||||
await page.getByRole('cell', { name: 'TO-0002' }).click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// This transfer order should be "issued"
|
||||
await page.getByText('Issued').first().waitFor();
|
||||
|
||||
// Edit the transfer order (via keyboard shortcut)
|
||||
await page.keyboard.press('Control+E');
|
||||
await page.getByLabel('text-field-reference', { exact: true }).waitFor();
|
||||
await page.getByLabel('related-field-project_code').waitFor();
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Complete Order' }).click();
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
|
||||
// Check for other expected actions
|
||||
await page.getByRole('button', { name: 'action-menu-order-actions' }).click();
|
||||
await page.getByLabel('action-menu-order-actions-edit').waitFor();
|
||||
await page.getByLabel('action-menu-order-actions-duplicate').waitFor();
|
||||
await page.getByLabel('action-menu-order-actions-hold').waitFor();
|
||||
|
||||
// Click on some tabs
|
||||
await loadTab(page, 'Line Items');
|
||||
await loadTab(page, 'Allocated Stock');
|
||||
await loadTab(page, 'Parameters');
|
||||
await loadTab(page, 'Attachments');
|
||||
await loadTab(page, 'Notes');
|
||||
});
|
||||
|
||||
test('Transfer Order - Reference', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
// go to transfer orders
|
||||
await page.getByRole('tab', { name: 'Stock' }).click();
|
||||
await page.waitForURL('**/stock/location/index/**');
|
||||
await loadTab(page, 'Transfer Orders');
|
||||
|
||||
// click add button
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-add-transfer-' })
|
||||
.click();
|
||||
|
||||
// Ensure a new reference is suggested
|
||||
await expect(
|
||||
page.getByLabel('text-field-reference', { exact: true })
|
||||
).not.toBeEmpty();
|
||||
// Grab the Transfer Order reference
|
||||
const reference: string = await page
|
||||
.getByRole('textbox', { name: 'text-field-reference' })
|
||||
.inputValue();
|
||||
expect(reference).toMatch(/TO-\d+/);
|
||||
|
||||
await page.getByRole('textbox', { name: 'text-field-description' }).click();
|
||||
await page
|
||||
.getByRole('textbox', { name: 'text-field-description' })
|
||||
.fill('creating from playwrigh!');
|
||||
|
||||
// create the transfer order
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByText('Item Created').waitFor();
|
||||
|
||||
// go back to stock page
|
||||
await page.getByRole('link', { name: 'Stock', exact: true }).click();
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-add-transfer-' })
|
||||
.click();
|
||||
|
||||
const nextReference: string = await page
|
||||
.getByRole('textbox', { name: 'text-field-reference' })
|
||||
.inputValue();
|
||||
expect(nextReference).toMatch(/TO-\d+/);
|
||||
|
||||
// Ensure that the reference has incremented
|
||||
const refNumber = Number(reference.replace('TO-', ''));
|
||||
const nextRefNumber = Number(nextReference.replace('TO-', ''));
|
||||
expect(nextRefNumber).toBe(refNumber + 1);
|
||||
});
|
||||
|
||||
test('Transfer Order - Calendar', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await navigate(page, 'stock/location/index/transfer-orders');
|
||||
await activateCalendarView(page);
|
||||
|
||||
// Export calendar data
|
||||
await page.getByLabel('calendar-export-data').click();
|
||||
await page.getByRole('button', { name: 'Export', exact: true }).click();
|
||||
await page.getByText('Process completed successfully').waitFor();
|
||||
|
||||
// Required because we downloaded a file
|
||||
await page.context().close();
|
||||
});
|
||||
|
||||
test('Transfer Order - Edit', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await navigate(page, 'stock/transfer-order/2/');
|
||||
|
||||
// Check for expected text items
|
||||
await page.getByText('Consume some paint').first().waitFor();
|
||||
await page.getByText('2026-04-20').waitFor(); // Created date
|
||||
await page.getByText('2026-04-23').waitFor(); // Issue date
|
||||
await page.getByText('PRJ-HEL').waitFor(); // Project Code
|
||||
|
||||
await page.keyboard.press('Control+E');
|
||||
|
||||
// Edit start date
|
||||
await page.getByLabel('date-field-start_date').fill('2026-04-28');
|
||||
|
||||
// Submit the form
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
// Expect error
|
||||
await page.getByText('Errors exist for one or more form fields').waitFor();
|
||||
await page.getByText('Target date must be after start date').waitFor();
|
||||
|
||||
// Cancel the form
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
|
||||
test('Transfer Order - Allocate and Transfer', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await navigate(page, 'stock/transfer-order/6/');
|
||||
|
||||
// Duplicate this transfer order, to ensure a fresh run each time
|
||||
await page.getByLabel('action-menu-order-actions').click();
|
||||
await page.getByLabel('action-menu-order-actions-duplicate').click();
|
||||
|
||||
// Submit the duplicate request and ensure it completes
|
||||
await page.getByRole('button', { name: 'Submit' }).isEnabled();
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByText('Item Created').waitFor();
|
||||
|
||||
// Issue the order
|
||||
await page.getByRole('button', { name: 'Issue Order' }).click();
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByText('Order issued').waitFor();
|
||||
|
||||
await loadTab(page, 'Line Items');
|
||||
|
||||
// Allocate line item 1
|
||||
const cell1 = await page.getByText('C_100pF_0402', { exact: true });
|
||||
await clickOnRowMenu(cell1);
|
||||
await page.getByRole('menuitem', { name: 'Allocate Stock' }).click();
|
||||
await page.getByText('C_100pF_0402Location:Offsite').waitFor();
|
||||
await page.waitForTimeout(200);
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
// Allocate line item 1
|
||||
const cell2 = await page.getByText('R_2.2K_0603_1%', { exact: true });
|
||||
await clickOnRowMenu(cell2);
|
||||
await page.getByRole('menuitem', { name: 'Allocate Stock' }).click();
|
||||
await page.getByText('R_2.2K_0603_1%Location:').waitFor();
|
||||
await page.waitForTimeout(200);
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
// Complete the order
|
||||
await page.getByRole('button', { name: 'Complete Order' }).click();
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByText('Complete', { exact: true }).first().waitFor();
|
||||
|
||||
// Tab should have changed to Transferred Stock
|
||||
await loadTab(page, 'Transferred Stock');
|
||||
await page.getByText('C_100pF_0402').waitFor();
|
||||
await page.getByText('2.2K resistor in 0603 SMD').waitFor();
|
||||
});
|
||||
|
||||
test('Transfer Orders - Duplicate', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
url: 'stock/transfer-order/1/detail'
|
||||
});
|
||||
|
||||
await page.getByLabel('action-menu-order-actions').click();
|
||||
await page.getByLabel('action-menu-order-actions-duplicate').click();
|
||||
|
||||
// Ensure a new reference is suggested
|
||||
await expect(
|
||||
page.getByLabel('text-field-reference', { exact: true })
|
||||
).not.toBeEmpty();
|
||||
|
||||
// Submit the duplicate request and ensure it completes
|
||||
await page.getByRole('button', { name: 'Submit' }).isEnabled();
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByRole('tab', { name: 'Order Details' }).waitFor();
|
||||
await page.getByRole('tab', { name: 'Order Details' }).click();
|
||||
|
||||
await page.getByText('Pending').first().waitFor();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user