mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 21:15:41 +00:00
Purchase Order Destination (#8403)
* Add "destination" field to PurchaseOrder * Add 'destination' field to API * Add location to PurchaseOrderDetail page * Display "destination" on PurchaseOrderDetail page * Pre-select location based on selected "destination" * Fix order of reception priority * Auto-expand the per-line destination field * Add "Purchase Order" detail to StockItemDetail page * Bug fix in PurchaseOrderForms * Split playwright tests * Docs updates * Bump API version * Unit test fixes * Fix more tests * Backport to CUI * Use PurchaseOrder destination when scanning items
This commit is contained in:
@ -166,6 +166,11 @@ export function usePurchaseOrderFields({
|
||||
target_date: {
|
||||
icon: <IconCalendar />
|
||||
},
|
||||
destination: {
|
||||
filters: {
|
||||
structural: false
|
||||
}
|
||||
},
|
||||
link: {},
|
||||
contact: {
|
||||
icon: <IconUser />,
|
||||
@ -232,6 +237,13 @@ function LineItemFormRow({
|
||||
onClose: () => props.changeFn(props.idx, 'location', undefined)
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!!record.destination) {
|
||||
props.changeFn(props.idx, 'location', record.destination);
|
||||
locationHandlers.open();
|
||||
}
|
||||
}, [record.destination]);
|
||||
|
||||
// Batch code generator
|
||||
const batchCodeGenerator = useBatchCodeGenerator((value: any) => {
|
||||
if (value) {
|
||||
@ -239,7 +251,7 @@ function LineItemFormRow({
|
||||
}
|
||||
});
|
||||
|
||||
// Serial numbebr generator
|
||||
// Serial number generator
|
||||
const serialNumberGenerator = useSerialNumberGenerator((value: any) => {
|
||||
if (value) {
|
||||
props.changeFn(props.idx, 'serial_numbers', value);
|
||||
@ -475,7 +487,7 @@ function LineItemFormRow({
|
||||
props.changeFn(props.idx, 'location', value);
|
||||
},
|
||||
description: locationDescription,
|
||||
value: location,
|
||||
value: props.item.location,
|
||||
label: t`Location`,
|
||||
icon: <InvenTreeIcon icon="location" />
|
||||
}}
|
||||
@ -599,6 +611,7 @@ type LineFormHandlers = {
|
||||
type LineItemsForm = {
|
||||
items: any[];
|
||||
orderPk: number;
|
||||
destinationPk?: number;
|
||||
formProps?: LineFormHandlers;
|
||||
};
|
||||
|
||||
@ -674,7 +687,7 @@ export function useReceiveLineItems(props: LineItemsForm) {
|
||||
title: t`Receive Line Items`,
|
||||
fields: fields,
|
||||
initialData: {
|
||||
location: null
|
||||
location: props.destinationPk
|
||||
},
|
||||
size: '80%'
|
||||
});
|
||||
|
@ -188,6 +188,14 @@ export default function BuildDetail() {
|
||||
label: t`Completed`,
|
||||
icon: 'calendar',
|
||||
hidden: !build.completion_date
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'project_code_label',
|
||||
label: t`Project Code`,
|
||||
icon: 'reference',
|
||||
copy: true,
|
||||
hidden: !build.project_code
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -153,12 +153,19 @@ export default function PurchaseOrderDetail() {
|
||||
total: order.line_items,
|
||||
progress: order.completed_lines
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
model: ModelType.stocklocation,
|
||||
link: true,
|
||||
name: 'destination',
|
||||
label: t`Destination`,
|
||||
hidden: !order.destination
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'currency',
|
||||
label: t`Order Currency`,
|
||||
value_formatter: () =>
|
||||
order?.order_currency ?? order?.supplier_detail?.currency
|
||||
value_formatter: () => orderCurrency
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
@ -190,8 +197,15 @@ export default function PurchaseOrderDetail() {
|
||||
icon: 'user',
|
||||
copy: true,
|
||||
hidden: !order.contact
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'project_code_label',
|
||||
label: t`Project Code`,
|
||||
icon: 'reference',
|
||||
copy: true,
|
||||
hidden: !order.project_code
|
||||
}
|
||||
// TODO: Project code
|
||||
];
|
||||
|
||||
let br: DetailsField[] = [
|
||||
@ -253,7 +267,7 @@ export default function PurchaseOrderDetail() {
|
||||
<DetailsTable fields={br} item={order} />
|
||||
</ItemDetailsGrid>
|
||||
);
|
||||
}, [order, instanceQuery]);
|
||||
}, [order, orderCurrency, instanceQuery]);
|
||||
|
||||
const orderPanels: PanelType[] = useMemo(() => {
|
||||
return [
|
||||
|
@ -167,8 +167,15 @@ export default function ReturnOrderDetail() {
|
||||
icon: 'user',
|
||||
copy: true,
|
||||
hidden: !order.contact
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'project_code_label',
|
||||
label: t`Project Code`,
|
||||
icon: 'reference',
|
||||
copy: true,
|
||||
hidden: !order.project_code
|
||||
}
|
||||
// TODO: Project code
|
||||
];
|
||||
|
||||
let br: DetailsField[] = [
|
||||
|
@ -179,8 +179,15 @@ export default function SalesOrderDetail() {
|
||||
icon: 'user',
|
||||
copy: true,
|
||||
hidden: !order.contact
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'project_code_label',
|
||||
label: t`Project Code`,
|
||||
icon: 'reference',
|
||||
copy: true,
|
||||
hidden: !order.project_code
|
||||
}
|
||||
// TODO: Project code
|
||||
];
|
||||
|
||||
let br: DetailsField[] = [
|
||||
|
@ -247,6 +247,15 @@ export default function StockDetail() {
|
||||
hidden: !stockitem.build,
|
||||
model_field: 'reference'
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
name: 'purchase_order',
|
||||
label: t`Purchase Order`,
|
||||
model: ModelType.purchaseorder,
|
||||
hidden: !stockitem.purchase_order,
|
||||
icon: 'purchase_orders',
|
||||
model_field: 'reference'
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
name: 'sales_order',
|
||||
|
@ -113,6 +113,7 @@ export function PurchaseOrderLineItemTable({
|
||||
const receiveLineItems = useReceiveLineItems({
|
||||
items: singleRecord ? [singleRecord] : table.selectedRecords,
|
||||
orderPk: orderId,
|
||||
destinationPk: order.destination,
|
||||
formProps: {
|
||||
// Timeout is a small hack to prevent function being called before re-render
|
||||
onClose: () => {
|
||||
|
85
src/frontend/tests/pages/pui_purchase_order.spec.ts
Normal file
85
src/frontend/tests/pages/pui_purchase_order.spec.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { test } from '../baseFixtures.ts';
|
||||
import { doQuickLogin } from '../login.ts';
|
||||
|
||||
test('Purchase Orders - General', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
await page.getByRole('tab', { name: 'Purchasing' }).click();
|
||||
await page.getByRole('cell', { name: 'PO0012' }).click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.getByRole('tab', { name: 'Line Items' }).click();
|
||||
await page.getByRole('tab', { name: 'Received Stock' }).click();
|
||||
await page.getByRole('tab', { name: 'Attachments' }).click();
|
||||
await page.getByRole('tab', { name: 'Purchasing' }).click();
|
||||
await page.getByRole('tab', { name: 'Suppliers' }).click();
|
||||
await page.getByText('Arrow', { exact: true }).click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.getByRole('tab', { name: 'Supplied Parts' }).click();
|
||||
await page.getByRole('tab', { name: 'Purchase Orders' }).click();
|
||||
await page.getByRole('tab', { name: 'Stock Items' }).click();
|
||||
await page.getByRole('tab', { name: 'Contacts' }).click();
|
||||
await page.getByRole('tab', { name: 'Addresses' }).click();
|
||||
await page.getByRole('tab', { name: 'Attachments' }).click();
|
||||
await page.getByRole('tab', { name: 'Purchasing' }).click();
|
||||
await page.getByRole('tab', { name: 'Manufacturers' }).click();
|
||||
await page.getByText('AVX Corporation').click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.getByRole('tab', { name: 'Addresses' }).click();
|
||||
await page.getByRole('cell', { name: 'West Branch' }).click();
|
||||
await page.locator('.mantine-ScrollArea-root').click();
|
||||
await page
|
||||
.getByRole('row', { name: 'West Branch Yes Surf Avenue 9' })
|
||||
.getByRole('button')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||
|
||||
await page.getByLabel('text-field-title').waitFor();
|
||||
await page.getByLabel('text-field-line2').waitFor();
|
||||
|
||||
// Read the current value of the cell, to ensure we always *change* it!
|
||||
const value = await page.getByLabel('text-field-line2').inputValue();
|
||||
await page
|
||||
.getByLabel('text-field-line2')
|
||||
.fill(value == 'old' ? 'new' : 'old');
|
||||
|
||||
await page.getByRole('button', { name: 'Submit' }).isEnabled();
|
||||
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByRole('tab', { name: 'Details' }).waitFor();
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests for receiving items against a purchase order
|
||||
*/
|
||||
test('Purchase Orders - Receive Items', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
await page.getByRole('tab', { name: 'Purchasing' }).click();
|
||||
await page.getByRole('cell', { name: 'PO0014' }).click();
|
||||
|
||||
await page.getByRole('tab', { name: 'Order Details' }).click();
|
||||
await page.getByText('0 / 3').waitFor();
|
||||
|
||||
// Select all line items to receive
|
||||
await page.getByRole('tab', { name: 'Line Items' }).click();
|
||||
|
||||
await page.getByLabel('Select all records').click();
|
||||
await page.waitForTimeout(200);
|
||||
await page.getByLabel('action-button-receive-items').click();
|
||||
|
||||
// Check for display of individual locations
|
||||
await page
|
||||
.getByRole('cell', { name: /Choose Location/ })
|
||||
.getByText('Parts Bins')
|
||||
.waitFor();
|
||||
await page
|
||||
.getByRole('cell', { name: /Choose Location/ })
|
||||
.getByText('Room 101')
|
||||
.waitFor();
|
||||
await page.getByText('Mechanical Lab').waitFor();
|
||||
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
@ -178,53 +178,3 @@ test('Purchase Orders - Barcodes', async ({ page }) => {
|
||||
await page.waitForTimeout(500);
|
||||
await page.getByRole('button', { name: 'Issue Order' }).waitFor();
|
||||
});
|
||||
|
||||
test('Purchase Orders - General', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
await page.getByRole('tab', { name: 'Purchasing' }).click();
|
||||
await page.getByRole('cell', { name: 'PO0012' }).click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.getByRole('tab', { name: 'Line Items' }).click();
|
||||
await page.getByRole('tab', { name: 'Received Stock' }).click();
|
||||
await page.getByRole('tab', { name: 'Attachments' }).click();
|
||||
await page.getByRole('tab', { name: 'Purchasing' }).click();
|
||||
await page.getByRole('tab', { name: 'Suppliers' }).click();
|
||||
await page.getByText('Arrow', { exact: true }).click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.getByRole('tab', { name: 'Supplied Parts' }).click();
|
||||
await page.getByRole('tab', { name: 'Purchase Orders' }).click();
|
||||
await page.getByRole('tab', { name: 'Stock Items' }).click();
|
||||
await page.getByRole('tab', { name: 'Contacts' }).click();
|
||||
await page.getByRole('tab', { name: 'Addresses' }).click();
|
||||
await page.getByRole('tab', { name: 'Attachments' }).click();
|
||||
await page.getByRole('tab', { name: 'Purchasing' }).click();
|
||||
await page.getByRole('tab', { name: 'Manufacturers' }).click();
|
||||
await page.getByText('AVX Corporation').click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.getByRole('tab', { name: 'Addresses' }).click();
|
||||
await page.getByRole('cell', { name: 'West Branch' }).click();
|
||||
await page.locator('.mantine-ScrollArea-root').click();
|
||||
await page
|
||||
.getByRole('row', { name: 'West Branch Yes Surf Avenue 9' })
|
||||
.getByRole('button')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||
|
||||
await page.getByLabel('text-field-title').waitFor();
|
||||
await page.getByLabel('text-field-line2').waitFor();
|
||||
|
||||
// Read the current value of the cell, to ensure we always *change* it!
|
||||
const value = await page.getByLabel('text-field-line2').inputValue();
|
||||
await page
|
||||
.getByLabel('text-field-line2')
|
||||
.fill(value == 'old' ? 'new' : 'old');
|
||||
|
||||
await page.getByRole('button', { name: 'Submit' }).isEnabled();
|
||||
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByRole('tab', { name: 'Details' }).waitFor();
|
||||
});
|
Reference in New Issue
Block a user