2
0
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:
Oliver
2025-11-16 21:59:27 +11:00
committed by GitHub
parent 2c508feeec
commit 770f7a292e
4 changed files with 72 additions and 17 deletions

View File

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

View File

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

View File

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

View File

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