diff --git a/src/frontend/lib/types/Forms.tsx b/src/frontend/lib/types/Forms.tsx index 79f55e52ad..43096ef968 100644 --- a/src/frontend/lib/types/Forms.tsx +++ b/src/frontend/lib/types/Forms.tsx @@ -56,6 +56,7 @@ export type ApiFormFieldHeader = { * @param description : The description to display for the field * @param preFieldContent : Content to render before the field * @param postFieldContent : Content to render after the field + * @param autoFill: Whether to automatically fill the field with data from the API * @param onValueChange : Callback function to call when the field value changes * @param adjustFilters : Callback function to adjust the filters for a related field before a query is made * @param adjustValue : Callback function to adjust the value of the field before it is sent to the API @@ -104,6 +105,7 @@ export type ApiFormFieldType = { description?: string; preFieldContent?: JSX.Element; postFieldContent?: JSX.Element; + autoFill?: boolean; adjustValue?: (value: any) => any; onValueChange?: (value: any, record?: any) => void; adjustFilters?: (value: ApiFormAdjustFilterType) => any; diff --git a/src/frontend/src/components/buttons/PrintingActions.tsx b/src/frontend/src/components/buttons/PrintingActions.tsx index 68e0e432c6..571cf0545c 100644 --- a/src/frontend/src/components/buttons/PrintingActions.tsx +++ b/src/frontend/src/components/buttons/PrintingActions.tsx @@ -80,6 +80,7 @@ export function PrintingActions({ // Override field values fields.template = { ...fields.template, + autoFill: true, filters: { enabled: true, model_type: modelType, @@ -132,6 +133,7 @@ export function PrintingActions({ timeout: 5000, fields: { template: { + autoFill: true, filters: { enabled: true, model_type: modelType, diff --git a/src/frontend/src/components/forms/fields/ApiFormField.tsx b/src/frontend/src/components/forms/fields/ApiFormField.tsx index 9e0a3bc2e5..5b3622d35c 100644 --- a/src/frontend/src/components/forms/fields/ApiFormField.tsx +++ b/src/frontend/src/components/forms/fields/ApiFormField.tsx @@ -72,6 +72,7 @@ export function ApiFormField({ const reducedDefinition = useMemo(() => { return { ...fieldDefinition, + autoFill: undefined, onValueChange: undefined, adjustFilters: undefined, adjustValue: undefined, diff --git a/src/frontend/src/components/forms/fields/RelatedModelField.tsx b/src/frontend/src/components/forms/fields/RelatedModelField.tsx index 1fb18c0b31..8d110017f5 100644 --- a/src/frontend/src/components/forms/fields/RelatedModelField.tsx +++ b/src/frontend/src/components/forms/fields/RelatedModelField.tsx @@ -54,6 +54,61 @@ export function RelatedModelField({ const [isOpen, setIsOpen] = useState(false); + // Auto-fill the field with data from the API + useEffect(() => { + // If there is *no value defined*, and autoFill is enabled, then fetch data from the API + if (!definition.autoFill || !definition.api_url) { + return; + } + + if (field.value != undefined) { + return; + } + + const params = definition?.filters ?? {}; + + api + .get(definition.api_url, { + params: { + ...params, + limit: 1, + offset: 0 + } + }) + .then((response) => { + const data: any = response?.data ?? {}; + + if (data.count === 1 && data.results?.length === 1) { + // If there is only a single result, set the field value to that result + const pk_field = definition.pk_field ?? 'pk'; + if (data.results[0][pk_field]) { + const value = { + value: data.results[0][pk_field], + data: data.results[0] + }; + + // Run custom callback for this field (if provided) + if (definition.onValueChange) { + definition.onValueChange( + data.results[0][pk_field], + data.results[0] + ); + } + + setInitialData(value); + dataRef.current = [value]; + setPk(data.results[0][pk_field]); + } + } + }); + }, [ + definition.autoFill, + definition.api_url, + definition.filters, + definition.pk_field, + field.value + ]); + // If an initial value is provided, load from the API useEffect(() => { // If the value is unchanged, do nothing @@ -98,7 +153,12 @@ export function RelatedModelField({ } else { setPk(null); } - }, [definition.api_url, definition.pk_field, field.value]); + }, [ + definition.api_url, + definition.filters, + definition.pk_field, + field.value + ]); // Search input query const [value, setValue] = useState(''); @@ -221,6 +281,7 @@ export function RelatedModelField({ const fieldDefinition = useMemo(() => { return { ...definition, + autoFill: undefined, onValueChange: undefined, adjustFilters: undefined, exclude: undefined, diff --git a/src/frontend/tests/pui_printing.spec.ts b/src/frontend/tests/pui_printing.spec.ts index 002456aed5..82ca8d187b 100644 --- a/src/frontend/tests/pui_printing.spec.ts +++ b/src/frontend/tests/pui_printing.spec.ts @@ -32,13 +32,12 @@ test('Label Printing', async ({ browser }) => { // Select label template await page.getByLabel('related-field-template').click(); - await page.getByText('InvenTree Stock Item Label (').click(); - - await page.waitForTimeout(100); + await page + .getByRole('option', { name: 'InvenTree Stock Item Label' }) + .click(); await page.getByLabel('related-field-plugin').click(); - - await page.getByText('InvenTreeLabel', { exact: true }).click(); + await page.getByRole('option', { name: 'InvenTreeLabel provides' }).click(); // Submit the print form (second time should result in success) await page.getByRole('button', { name: 'Print', exact: true }).isEnabled(); @@ -71,9 +70,7 @@ test('Report Printing', async ({ browser }) => { // Select template await page.getByLabel('related-field-template').click(); - await page.getByText('InvenTree Purchase Order').click(); - - await page.waitForTimeout(100); + await page.getByRole('option', { name: 'InvenTree Purchase Order' }).click(); // Submit the print form (should result in success) await page.getByRole('button', { name: 'Print', exact: true }).isEnabled();