mirror of
https://github.com/inventree/InvenTree.git
synced 2026-04-06 11:31:04 +00:00
412 lines
14 KiB
TypeScript
412 lines
14 KiB
TypeScript
import { expect } from '@playwright/test';
|
|
import { test } from '../baseFixtures.ts';
|
|
import {
|
|
clearTableFilters,
|
|
clickOnRowMenu,
|
|
globalSearch,
|
|
loadTab,
|
|
navigate,
|
|
setTableChoiceFilter,
|
|
showCalendarView,
|
|
showParametricView,
|
|
showTableView
|
|
} from '../helpers.ts';
|
|
import { doCachedLogin } from '../login.ts';
|
|
|
|
test('Sales Orders - Tabs', async ({ browser }) => {
|
|
const page = await doCachedLogin(browser, { url: 'sales/index/' });
|
|
|
|
await page.waitForURL('**/web/sales/**');
|
|
|
|
// Sales Orders panel
|
|
await loadTab(page, 'Sales Orders');
|
|
await page.waitForURL('**/web/sales/index/salesorders');
|
|
|
|
await showParametricView(page);
|
|
await showCalendarView(page);
|
|
await showTableView(page);
|
|
|
|
// Pending Shipments panel
|
|
await loadTab(page, 'Pending Shipments');
|
|
await page.getByRole('cell', { name: 'SO0007' }).waitFor();
|
|
await page.getByRole('button', { name: 'Shipment Reference' }).waitFor();
|
|
|
|
// Return Orders panel
|
|
await loadTab(page, 'Return Orders');
|
|
await page.getByRole('cell', { name: 'NOISE-COMPLAINT' }).waitFor();
|
|
|
|
await showParametricView(page);
|
|
await showCalendarView(page);
|
|
await showTableView(page);
|
|
|
|
// Customers
|
|
await loadTab(page, 'Customers');
|
|
|
|
await showParametricView(page);
|
|
await showTableView(page);
|
|
|
|
await page.getByText('Customer A').click();
|
|
await loadTab(page, 'Notes');
|
|
await loadTab(page, 'Attachments');
|
|
await loadTab(page, 'Contacts');
|
|
await loadTab(page, 'Assigned Stock');
|
|
await loadTab(page, 'Return Orders');
|
|
await loadTab(page, 'Sales Orders');
|
|
await loadTab(page, 'Contacts');
|
|
await page.getByRole('cell', { name: 'Dorathy Gross' }).waitFor();
|
|
await page
|
|
.getByRole('row', { name: 'Dorathy Gross dorathy.gross@customer.com' })
|
|
.waitFor();
|
|
|
|
// Sales Order Details
|
|
await loadTab(page, 'Sales Orders');
|
|
|
|
await clearTableFilters(page);
|
|
|
|
await page.getByRole('cell', { name: 'SO0001' }).click();
|
|
await page
|
|
.getByLabel('Order Details')
|
|
.getByText('Selling some stuff')
|
|
.waitFor();
|
|
await loadTab(page, 'Line Items');
|
|
await loadTab(page, 'Shipments');
|
|
await loadTab(page, 'Build Orders');
|
|
await page.getByText('No records found').first().waitFor();
|
|
await loadTab(page, 'Attachments');
|
|
await page.getByText('No attachments found').first().waitFor();
|
|
await loadTab(page, 'Notes');
|
|
await loadTab(page, 'Order Details');
|
|
|
|
// Return Order Details
|
|
await page.getByRole('link', { name: 'Customer A' }).click();
|
|
await loadTab(page, 'Return Orders');
|
|
await page.getByRole('cell', { name: 'RMA-' }).click();
|
|
await page.getByText('RMA-0001', { exact: true }).waitFor();
|
|
await loadTab(page, 'Line Items');
|
|
await loadTab(page, 'Attachments');
|
|
await loadTab(page, 'Notes');
|
|
});
|
|
|
|
test('Sales Orders - Basic Tests', async ({ browser }) => {
|
|
const page = await doCachedLogin(browser);
|
|
|
|
await page.getByRole('tab', { name: 'Sales' }).click();
|
|
await page.waitForURL('**/sales/index/**');
|
|
|
|
await loadTab(page, 'Sales Orders');
|
|
|
|
await clearTableFilters(page);
|
|
|
|
await setTableChoiceFilter(page, 'status', 'On Hold');
|
|
|
|
await page.getByText('On Hold').first().waitFor();
|
|
|
|
// Navigate to a particular sales order
|
|
await page.getByRole('cell', { name: 'SO0003' }).click();
|
|
|
|
// Order is "on hold". We will "issue" it and then place on hold again
|
|
await page.getByText('Selling stuff').first().waitFor();
|
|
await page.getByText('On Hold').first().waitFor();
|
|
await page.getByRole('button', { name: 'Issue Order' }).click();
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
|
|
// Order should now be "in progress"
|
|
await page.getByText('In Progress').first().waitFor();
|
|
await page.getByRole('button', { name: 'Ship Order' }).waitFor();
|
|
|
|
await page.getByLabel('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-cancel').waitFor();
|
|
|
|
// Mark the order as "on hold" again
|
|
await page.getByLabel('action-menu-order-actions-hold').click();
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
|
|
await page.getByText('On Hold').first().waitFor();
|
|
await page.getByRole('button', { name: 'Issue Order' }).waitFor();
|
|
});
|
|
|
|
test('Sales Orders - Shipments', async ({ browser }) => {
|
|
const page = await doCachedLogin(browser);
|
|
|
|
await page.getByRole('tab', { name: 'Sales' }).click();
|
|
await page.waitForURL('**/sales/index/**');
|
|
|
|
await loadTab(page, 'Sales Orders');
|
|
|
|
await clearTableFilters(page);
|
|
|
|
// Click through to a particular sales order
|
|
await page.getByRole('cell', { name: 'SO0006' }).first().click();
|
|
await loadTab(page, 'Shipments');
|
|
await clearTableFilters(page);
|
|
|
|
// Create a new shipment
|
|
await page.getByLabel('action-button-add-shipment').click();
|
|
await page
|
|
.getByLabel('text-field-tracking_number', { exact: true })
|
|
.fill('1234567890');
|
|
await page
|
|
.getByLabel('text-field-invoice_number', { exact: true })
|
|
.fill('9876543210');
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
|
|
// Expected field error
|
|
await page
|
|
.getByText('The fields order, reference must make a unique set')
|
|
.first()
|
|
.waitFor();
|
|
await page.getByRole('button', { name: 'Cancel' }).click();
|
|
|
|
// Edit one of the existing shipments
|
|
const cell = page.getByRole('cell', { name: /SHIP-XYZ/ });
|
|
await clickOnRowMenu(cell);
|
|
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
|
|
|
// Ensure the form has loaded
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
let tracking_number = await page
|
|
.getByLabel('text-field-tracking_number', { exact: true })
|
|
.inputValue();
|
|
|
|
if (!tracking_number) {
|
|
tracking_number = '1234567890';
|
|
} else if (tracking_number.endsWith('x')) {
|
|
// Remove the 'x' from the end of the tracking number
|
|
tracking_number = tracking_number.substring(0, tracking_number.length - 1);
|
|
} else {
|
|
// Add an 'x' to the end of the tracking number
|
|
tracking_number += 'x';
|
|
}
|
|
|
|
// Change the tracking number
|
|
await page
|
|
.getByLabel('text-field-tracking_number', { exact: true })
|
|
.fill(tracking_number);
|
|
await page.waitForTimeout(250);
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
|
|
// Click through to a particular shipment
|
|
await page.getByRole('cell', { name: tracking_number }).click();
|
|
|
|
// Click through the various tabs
|
|
await loadTab(page, 'Parameters');
|
|
await loadTab(page, 'Attachments');
|
|
await loadTab(page, 'Notes');
|
|
await loadTab(page, 'Allocated Stock');
|
|
|
|
// Ensure assigned items table loads correctly
|
|
await page.getByRole('cell', { name: 'BATCH-001' }).first().waitFor();
|
|
|
|
await loadTab(page, 'Shipment Details');
|
|
|
|
// The "new" tracking number should be visible
|
|
await page.getByText(tracking_number).waitFor();
|
|
|
|
// Link back to sales order
|
|
await page.getByRole('link', { name: 'breadcrumb-1-so0006' }).click();
|
|
|
|
// Let's try to allocate some stock
|
|
await loadTab(page, 'Line Items');
|
|
|
|
await clickOnRowMenu(page.getByRole('cell', { name: 'WID-REV-A' }));
|
|
await page.getByRole('menuitem', { name: 'Allocate stock' }).click();
|
|
await page
|
|
.getByText('Select the source location for the stock allocation')
|
|
.waitFor();
|
|
await page.getByLabel('number-field-quantity').fill('123');
|
|
await page.getByLabel('related-field-stock_item').fill('42');
|
|
|
|
await page.getByText('Serial Number: 42').click();
|
|
await page.getByRole('button', { name: 'Cancel' }).click();
|
|
|
|
// Search for shipment by tracking number
|
|
await globalSearch(page, 'TRK-002');
|
|
|
|
await page
|
|
.getByText(/SO0009/)
|
|
.first()
|
|
.click();
|
|
|
|
// Search for shipment by invoice number
|
|
await globalSearch(page, 'INV-123');
|
|
|
|
await page
|
|
.getByText(/SO0025/)
|
|
.first()
|
|
.click();
|
|
});
|
|
|
|
// Complete a shipment against a sales order
|
|
test('Sales Orders - Complete Shipment', async ({ browser }) => {
|
|
const page = await doCachedLogin(browser, {
|
|
url: 'part/113/stock'
|
|
});
|
|
|
|
const serialNumber = `SN${Math.floor(Math.random() * 100000)}`;
|
|
const shipmentReference = `SHIP-${Math.floor(Math.random() * 100000)}`;
|
|
|
|
// First create some stock to allocate
|
|
await page
|
|
.getByRole('button', { name: 'action-button-add-stock-item' })
|
|
.click();
|
|
await page
|
|
.getByRole('textbox', { name: 'text-field-serial_numbers' })
|
|
.fill(serialNumber);
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
await page.getByText('Stock item created').first().waitFor();
|
|
|
|
// Navigate to the sales order and create a new shipment
|
|
await navigate(page, '/sales/sales-order/7/shipments');
|
|
await page
|
|
.getByRole('button', { name: 'action-button-add-shipment' })
|
|
.click();
|
|
await page
|
|
.getByLabel('text-field-reference', { exact: true })
|
|
.fill(shipmentReference);
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
await page.getByText('Shipment created').first().waitFor();
|
|
|
|
// Back to the "line items" tab to allocate stock
|
|
await loadTab(page, 'Line Items');
|
|
const cell = await page.getByRole('cell', { name: 'MAST', exact: true });
|
|
await clickOnRowMenu(cell);
|
|
|
|
// Allocate 1 item based on serial number
|
|
await page.getByRole('menuitem', { name: 'Allocate serials' }).click();
|
|
await page.getByRole('textbox', { name: 'number-field-quantity' }).fill('1');
|
|
await page
|
|
.getByRole('textbox', { name: 'text-field-serial_numbers' })
|
|
.fill(serialNumber);
|
|
await page.getByLabel('related-field-shipment').fill(shipmentReference);
|
|
await page.getByText(`SO0007Shipment ${shipmentReference}`).click();
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
await page.getByText('Stock allocated successfully').first().waitFor();
|
|
|
|
// Navigate to the shipment and mark it as "shipped"
|
|
await loadTab(page, 'Shipments');
|
|
await page.getByRole('cell', { name: shipmentReference }).click();
|
|
await page.getByText(shipmentReference).first().waitFor();
|
|
await page.getByText('Pending').first().waitFor();
|
|
await loadTab(page, 'Allocated Stock');
|
|
|
|
// Check that the serial number is allocated as expected
|
|
await page.getByRole('cell', { name: serialNumber }).waitFor();
|
|
await page.getByRole('button', { name: 'Send Shipment' }).click();
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
|
|
await page.getByText('Completing shipment').first().waitFor();
|
|
await page.getByText('Shipment completed').first().waitFor();
|
|
|
|
await page.getByText('Shipped', { exact: true }).first().waitFor();
|
|
|
|
// Finally, navigate to the stock item and check it has been allocated to the customer
|
|
await page.getByRole('cell', { name: serialNumber }).click();
|
|
await page.waitForLoadState('networkidle');
|
|
await page.getByText('Unavailable').first().waitFor();
|
|
await page.getByRole('link', { name: 'SO0007' }).waitFor();
|
|
await page.getByRole('cell', { name: 'Customer D' }).waitFor();
|
|
});
|
|
|
|
test('Sales Orders - Duplicate', async ({ browser }) => {
|
|
const page = await doCachedLogin(browser, {
|
|
url: 'sales/sales-order/14/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();
|
|
|
|
// Issue the order
|
|
await page.getByRole('button', { name: 'Issue Order' }).click();
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
await page.getByText('In Progress').first().waitFor();
|
|
|
|
// Cancel the outstanding shipment
|
|
await loadTab(page, 'Shipments');
|
|
await clearTableFilters(page);
|
|
const cell = await page.getByRole('cell', { name: '1', exact: true });
|
|
await clickOnRowMenu(cell);
|
|
await page.getByRole('menuitem', { name: 'Cancel' }).click();
|
|
await page.getByRole('button', { name: 'Delete' }).click();
|
|
|
|
// Check for expected line items
|
|
await loadTab(page, 'Line Items');
|
|
await page.getByRole('cell', { name: 'SW-001' }).waitFor();
|
|
await page.getByRole('cell', { name: 'SW-002' }).waitFor();
|
|
await page.getByText('1 - 2 / 2').waitFor();
|
|
|
|
// Ship the order
|
|
await page.getByRole('button', { name: 'Ship Order' }).click();
|
|
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();
|
|
|
|
// Go to the "details" tab
|
|
await loadTab(page, 'Order Details');
|
|
|
|
// Check for expected results
|
|
// 2 line items completed, as they are both virtual (no stock)
|
|
await page.getByText('Complete').first().waitFor();
|
|
await page.getByText('2 / 2').waitFor();
|
|
});
|
|
|
|
/**
|
|
* Test auto-calculation of price breaks on sales order line items
|
|
*/
|
|
test('Sales Orders - Price Breaks', async ({ browser }) => {
|
|
const page = await doCachedLogin(browser, {
|
|
url: 'sales/sales-order/14/line-items'
|
|
});
|
|
|
|
await page
|
|
.getByRole('button', { name: 'action-button-add-line-item' })
|
|
.click();
|
|
await page.getByLabel('related-field-part').fill('software');
|
|
await page.getByRole('option', { name: 'Software License' }).click();
|
|
|
|
const priceBreaks = {
|
|
1: 123,
|
|
2: 123,
|
|
10: 104,
|
|
49: 104,
|
|
56: 96,
|
|
104: 78
|
|
};
|
|
|
|
for (const [qty, expectedPrice] of Object.entries(priceBreaks)) {
|
|
await page.getByLabel('number-field-quantity').fill(qty);
|
|
|
|
await expect(
|
|
page.getByRole('textbox', { name: 'number-field-sale_price' })
|
|
).toHaveAttribute('placeholder', expectedPrice.toString(), {
|
|
timeout: 500
|
|
});
|
|
}
|
|
|
|
// Auto-fill the suggested sale price
|
|
await page.getByLabel('field-sale_price-accept-placeholder').click();
|
|
|
|
// The sale price field should now contain the suggested value
|
|
await expect(
|
|
page.getByRole('textbox', { name: 'number-field-sale_price' })
|
|
).toHaveValue('78', { timeout: 500 });
|
|
});
|