mirror of
https://github.com/inventree/InvenTree.git
synced 2025-12-16 09:18:10 +00:00
Fix for shipping virtual parts (#10853)
* Additional checks for virtual parts in sales order process * Prevent allocation against virtual parts * Fix order of operations * Adjust part form fields based on selections * Prevent order locking * Updated playwright tests * Add unit test
This commit is contained in:
@@ -20,6 +20,9 @@ export function usePartFields({
|
||||
}): ApiFormFieldSet {
|
||||
const settings = useGlobalSettingsState();
|
||||
|
||||
const [virtual, setVirtual] = useState<boolean>(false);
|
||||
const [purchaseable, setPurchaseable] = useState<boolean>(false);
|
||||
|
||||
return useMemo(() => {
|
||||
const fields: ApiFormFieldSet = {
|
||||
category: {
|
||||
@@ -62,9 +65,19 @@ export function usePartFields({
|
||||
is_template: {},
|
||||
testable: {},
|
||||
trackable: {},
|
||||
purchaseable: {},
|
||||
purchaseable: {
|
||||
value: purchaseable,
|
||||
onValueChange: (value: boolean) => {
|
||||
setPurchaseable(value);
|
||||
}
|
||||
},
|
||||
salable: {},
|
||||
virtual: {},
|
||||
virtual: {
|
||||
value: virtual,
|
||||
onValueChange: (value: boolean) => {
|
||||
setVirtual(value);
|
||||
}
|
||||
},
|
||||
locked: {},
|
||||
active: {},
|
||||
starred: {
|
||||
@@ -80,33 +93,37 @@ export function usePartFields({
|
||||
if (create) {
|
||||
fields.copy_category_parameters = {};
|
||||
|
||||
fields.initial_stock = {
|
||||
icon: <IconPackages />,
|
||||
children: {
|
||||
quantity: {
|
||||
value: 0
|
||||
},
|
||||
location: {}
|
||||
}
|
||||
};
|
||||
if (!virtual) {
|
||||
fields.initial_stock = {
|
||||
icon: <IconPackages />,
|
||||
children: {
|
||||
quantity: {
|
||||
value: 0
|
||||
},
|
||||
location: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fields.initial_supplier = {
|
||||
icon: <IconBuildingStore />,
|
||||
children: {
|
||||
supplier: {
|
||||
filters: {
|
||||
is_supplier: true
|
||||
}
|
||||
},
|
||||
sku: {},
|
||||
manufacturer: {
|
||||
filters: {
|
||||
is_manufacturer: true
|
||||
}
|
||||
},
|
||||
mpn: {}
|
||||
}
|
||||
};
|
||||
if (purchaseable) {
|
||||
fields.initial_supplier = {
|
||||
icon: <IconBuildingStore />,
|
||||
children: {
|
||||
supplier: {
|
||||
filters: {
|
||||
is_supplier: true
|
||||
}
|
||||
},
|
||||
sku: {},
|
||||
manufacturer: {
|
||||
filters: {
|
||||
is_manufacturer: true
|
||||
}
|
||||
},
|
||||
mpn: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Additional fields for part duplication
|
||||
@@ -159,7 +176,7 @@ export function usePartFields({
|
||||
}
|
||||
|
||||
return fields;
|
||||
}, [create, duplicatePartInstance, settings]);
|
||||
}, [virtual, purchaseable, create, duplicatePartInstance, settings]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -320,7 +320,9 @@ export default function SalesOrderLineItemTable({
|
||||
|
||||
const allocateStock = useAllocateToSalesOrderForm({
|
||||
orderId: orderId,
|
||||
lineItems: selectedItems,
|
||||
lineItems: selectedItems.filter(
|
||||
(item) => item.part_detail?.virtual !== true
|
||||
),
|
||||
onFormSuccess: () => {
|
||||
table.refreshTable();
|
||||
table.clearSelectedRecords();
|
||||
|
||||
@@ -225,7 +225,7 @@ test('Sales Orders - Shipments', async ({ browser }) => {
|
||||
|
||||
test('Sales Orders - Duplicate', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
url: 'sales/sales-order/11/detail'
|
||||
url: 'sales/sales-order/14/detail'
|
||||
});
|
||||
|
||||
await page.getByLabel('action-menu-order-actions').click();
|
||||
@@ -243,4 +243,39 @@ test('Sales Orders - Duplicate', async ({ browser }) => {
|
||||
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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user