2
0
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:
Oliver
2024-11-01 11:10:25 +11:00
committed by GitHub
parent 871cd905f1
commit c4031dba7f
20 changed files with 240 additions and 71 deletions

View File

@ -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%'
});

View File

@ -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
}
];

View File

@ -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 [

View File

@ -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[] = [

View File

@ -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[] = [

View File

@ -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',

View File

@ -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: () => {

View 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();
});

View File

@ -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();
});