mirror of
https://github.com/inventree/InvenTree.git
synced 2025-11-30 01:10:00 +00:00
[UI] Fix for form OPTIONS query (#10840)
* [UI] Fix for form OPTIONS query - Fetch OPTIONs each time form is opened - Ensure default values are filled correctly - Prevent issues with latching form state * Add comment * Add playwright test - Check that the reference field increments properly * Fix other Playwright tests
This commit is contained in:
@@ -24,11 +24,7 @@ import { type NavigateFunction, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { isTrue } from '@lib/functions/Conversion';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import type {
|
||||
ApiFormFieldSet,
|
||||
ApiFormFieldType,
|
||||
ApiFormProps
|
||||
} from '@lib/types/Forms';
|
||||
import type { ApiFormFieldSet, ApiFormProps } from '@lib/types/Forms';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import {
|
||||
type NestedDict,
|
||||
@@ -46,9 +42,11 @@ import { ApiFormField } from './fields/ApiFormField';
|
||||
|
||||
export function OptionsApiForm({
|
||||
props: _props,
|
||||
opened,
|
||||
id: pId
|
||||
}: Readonly<{
|
||||
props: ApiFormProps;
|
||||
opened?: boolean;
|
||||
id?: string;
|
||||
}>) {
|
||||
const api = useApi();
|
||||
@@ -75,24 +73,26 @@ export function OptionsApiForm({
|
||||
);
|
||||
|
||||
const optionsQuery = useQuery({
|
||||
enabled: true,
|
||||
enabled: opened !== false && props.ignorePermissionCheck !== true,
|
||||
refetchOnMount: false,
|
||||
queryKey: [
|
||||
'form-options-data',
|
||||
id,
|
||||
opened,
|
||||
props.ignorePermissionCheck,
|
||||
props.method,
|
||||
props.url,
|
||||
props.pk,
|
||||
props.pathParams
|
||||
],
|
||||
queryFn: async () => {
|
||||
const response = await api.options(url);
|
||||
let fields: Record<string, ApiFormFieldType> | null = {};
|
||||
if (!props.ignorePermissionCheck) {
|
||||
fields = extractAvailableFields(response, props.method);
|
||||
if (props.ignorePermissionCheck === true || opened === false) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return fields;
|
||||
return api.options(url).then((response: any) => {
|
||||
return extractAvailableFields(response, props.method);
|
||||
});
|
||||
},
|
||||
throwOnError: (error: any) => {
|
||||
if (error.response) {
|
||||
@@ -110,6 +110,13 @@ export function OptionsApiForm({
|
||||
}
|
||||
});
|
||||
|
||||
// Refetch form options whenever the modal is opened
|
||||
useEffect(() => {
|
||||
if (opened !== false) {
|
||||
optionsQuery.refetch();
|
||||
}
|
||||
}, [opened]);
|
||||
|
||||
const formProps: ApiFormProps = useMemo(() => {
|
||||
const _props = { ...props };
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Alert, Divider, Stack } from '@mantine/core';
|
||||
import { useId } from '@mantine/hooks';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import type {
|
||||
ApiFormModalProps,
|
||||
@@ -50,14 +50,18 @@ export function useApiFormModal(props: ApiFormModalProps) {
|
||||
[props]
|
||||
);
|
||||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
const modal = useModal({
|
||||
id: modalId,
|
||||
title: formProps.title,
|
||||
onOpen: () => {
|
||||
setIsOpen(true);
|
||||
modalState.setModalOpen(modalId, true);
|
||||
formProps.onOpen?.();
|
||||
},
|
||||
onClose: () => {
|
||||
setIsOpen(false);
|
||||
modalState.setModalOpen(modalId, false);
|
||||
formProps.onClose?.();
|
||||
},
|
||||
@@ -66,7 +70,7 @@ export function useApiFormModal(props: ApiFormModalProps) {
|
||||
children: (
|
||||
<Stack gap={'xs'}>
|
||||
<Divider />
|
||||
<OptionsApiForm props={formProps} id={modalId} />
|
||||
<OptionsApiForm props={formProps} id={modalId} opened={isOpen} />
|
||||
</Stack>
|
||||
)
|
||||
});
|
||||
|
||||
@@ -99,6 +99,50 @@ test('Build Order - Basic Tests', async ({ browser }) => {
|
||||
.waitFor();
|
||||
});
|
||||
|
||||
// Test that the build order reference field increments correctly
|
||||
test('Build Order - Reference', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
url: 'manufacturing/index/buildorders'
|
||||
});
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-add-build-order' })
|
||||
.click();
|
||||
await page.getByRole('button', { name: 'Submit' }).waitFor();
|
||||
|
||||
// Grab the next BuildOrder reference
|
||||
const reference: string = await page
|
||||
.getByRole('textbox', { name: 'text-field-reference' })
|
||||
.inputValue();
|
||||
expect(reference).toMatch(/BO\d+/);
|
||||
|
||||
// Select a part
|
||||
await page.getByLabel('related-field-part').fill('MAST');
|
||||
await page.getByText('MAST | Master Assembly').click();
|
||||
|
||||
// Submit the form
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByText('Item Created').waitFor();
|
||||
|
||||
// Back to the "build order" page - to create a new order
|
||||
await navigate(page, 'manufacturing/index/buildorders');
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-add-build-order' })
|
||||
.click();
|
||||
await page.getByRole('button', { name: 'Submit' }).waitFor();
|
||||
|
||||
const nextReference: string = await page
|
||||
.getByRole('textbox', { name: 'text-field-reference' })
|
||||
.inputValue();
|
||||
expect(nextReference).toMatch(/BO\d+/);
|
||||
|
||||
// Ensure that the reference has incremented
|
||||
const refNumber = Number(reference.replace('BO', ''));
|
||||
const nextRefNumber = Number(nextReference.replace('BO', ''));
|
||||
expect(nextRefNumber).toBe(refNumber + 1);
|
||||
});
|
||||
|
||||
test('Build Order - Calendar', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ test('Parts - Details', async ({ browser }) => {
|
||||
|
||||
// Depending on the state of other tests, the "In Production" value may vary
|
||||
// This could be either 4 / 49, or 5 / 49
|
||||
await page.getByText(/[4|5] \/ 49/).waitFor();
|
||||
await page.getByText(/[4|5] \/ \d+/).waitFor();
|
||||
|
||||
// Badges
|
||||
await page.getByText('Required: 10').waitFor();
|
||||
@@ -232,14 +232,14 @@ test('Parts - Requirements', async ({ browser }) => {
|
||||
// Check top-level badges
|
||||
await page.getByText('In Stock: 209').waitFor();
|
||||
await page.getByText('Available: 204').waitFor();
|
||||
await page.getByText('Required: 275').waitFor();
|
||||
await page.getByText(/Required: 2\d+/).waitFor();
|
||||
await page.getByText('In Production: 24').waitFor();
|
||||
|
||||
// Check requirements details
|
||||
await page.getByText('204 / 209').waitFor(); // Available stock
|
||||
await page.getByText('0 / 100').waitFor(); // Allocated to build orders
|
||||
await page.getByText(/0 \/ 1\d+/).waitFor(); // Allocated to build orders
|
||||
await page.getByText('5 / 175').waitFor(); // Allocated to sales orders
|
||||
await page.getByText('24 / 214').waitFor(); // In production
|
||||
await page.getByText(/24 \/ 2\d+/).waitFor(); // In production
|
||||
|
||||
// Let's check out the "variants" for this part, too
|
||||
await navigate(page, 'part/81/details'); // WID-REV-A
|
||||
|
||||
Reference in New Issue
Block a user