2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-09-13 22:21:37 +00:00

Bulk add test results (#10146)

* Bulk creation of test results

- Add BulkCreateMixin class
- Add frontend support

* Refactor test result serializer

- Allow lookup by template name

* Updated unit test

* Add unit tests

* Add row actions

* Docs

* Fix failing tests

* Bump API version

* Fix playwright tests
This commit is contained in:
Oliver
2025-08-08 07:19:20 +10:00
committed by GitHub
parent 7df8e06f36
commit 00017400ff
14 changed files with 237 additions and 78 deletions

View File

@@ -356,12 +356,7 @@ export function ApiForm({
let hasFiles = false;
// Optionally pre-process the data before submitting it
if (props.processFormData) {
data = props.processFormData(data, form);
}
const jsonData = { ...data };
let jsonData = { ...data };
const formData = new FormData();
Object.keys(data).forEach((key: string) => {
@@ -397,6 +392,11 @@ export function ApiForm({
}
});
// Optionally pre-process the data before submitting it
if (props.processFormData) {
jsonData = props.processFormData(jsonData, form);
}
/* Set the timeout for the request:
* - If a timeout is provided in the props, use that
* - If the form contains files, use a longer timeout

View File

@@ -209,6 +209,10 @@ export function RenderInlineModel({
}
}
if (typeof suffix === 'string') {
suffix = <Text size='xs'>{suffix}</Text>;
}
return (
<Group gap='xs' justify='space-between' wrap='nowrap' title={tooltip}>
<Group gap='xs' justify='left' wrap='nowrap'>
@@ -226,7 +230,7 @@ export function RenderInlineModel({
{suffix && (
<>
<Space />
<div style={{ fontSize: 'xs', lineHeight: 'xs' }}>{suffix}</div>
{suffix}
</>
)}
</Group>

View File

@@ -125,7 +125,7 @@ export function RenderPartTestTemplate({
return (
<RenderInlineModel
primary={instance.test_name}
secondary={instance.description}
suffix={instance.description}
/>
);
}

View File

@@ -237,8 +237,7 @@ export default function SystemSettings() {
'STOCK_SHOW_INSTALLED_ITEMS',
'STOCK_ENFORCE_BOM_INSTALLATION',
'STOCK_ALLOW_OUT_OF_STOCK_TRANSFER',
'TEST_STATION_DATA',
'TEST_UPLOAD_CREATE_TEMPLATE'
'TEST_STATION_DATA'
]}
/>
)

View File

@@ -2,16 +2,24 @@ import { t } from '@lingui/core/macro';
import { ActionIcon, Badge, Group, Text, Tooltip } from '@mantine/core';
import { IconCirclePlus } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { type ReactNode, useEffect, useMemo, useState } from 'react';
import {
type ReactNode,
useCallback,
useEffect,
useMemo,
useState
} from 'react';
import { PassFailButton } from '@lib/components/YesNoButton';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api';
import { cancelEvent } from '@lib/functions/Events';
import { AddItemButton } from '@lib/index';
import type { TableFilter } from '@lib/types/Filters';
import type { ApiFormFieldSet } from '@lib/types/Forms';
import type { TableColumn } from '@lib/types/Tables';
import type { UseFormReturn } from 'react-hook-form';
import { RenderUser } from '../../components/render/User';
import { useApi } from '../../contexts/ApiContext';
import { formatDate } from '../../defaults/formatters';
@@ -62,7 +70,9 @@ export default function BuildOrderTestTable({
}, [testTemplates]);
const [selectedOutput, setSelectedOutput] = useState<number>(0);
const [selectedTemplate, setSelectedTemplate] = useState<number>(0);
const [selectedTemplate, setSelectedTemplate] = useState<number | undefined>(
undefined
);
const testResultFields: ApiFormFieldSet = useTestResultFields({
partId: partId,
@@ -82,6 +92,48 @@ export default function BuildOrderTestTable({
successMessage: t`Test result added`
});
const multipleTestResultFields: ApiFormFieldSet = useMemo(() => {
const fields: ApiFormFieldSet = { ...testResultFields };
// Do not allow attachment for multiple test results
delete fields.attachment;
delete fields.stock_item;
fields.template.disabled = false;
return fields;
}, [partId, testResultFields]);
const generateTestResults = useCallback(
(data: any, form: UseFormReturn) => {
// Generate a list of test results for each selected output
const results = table.selectedRecords.map((record: any) => {
return {
...data,
stock_item: record.pk
};
});
return results;
},
[table.selectedIds]
);
const createTestResultMultiple = useCreateApiFormModal({
url: apiUrl(ApiEndpoints.stock_test_result_list),
title: t`Add Test Results`,
fields: multipleTestResultFields,
initialData: {
result: true
},
onFormSuccess: () => {
table.clearSelectedRecords();
table.refreshTable();
},
processFormData: generateTestResults,
successMessage: t`Test results added`
});
// Generate a table column for each test template
const testColumns: TableColumn[] = useMemo(() => {
if (!testTemplates || testTemplates.length == 0) {
@@ -112,6 +164,7 @@ export default function BuildOrderTestTable({
<ActionIcon
size='lg'
color='green'
aria-label='add-test-result'
variant='transparent'
onClick={(event: any) => {
cancelEvent(event);
@@ -224,12 +277,37 @@ export default function BuildOrderTestTable({
}, []);
const tableActions = useMemo(() => {
return [];
return [
<AddItemButton
key='add-test-result'
tooltip={t`Add Test Result`}
disabled={!table.hasSelectedRecords}
onClick={(event: any) => {
createTestResultMultiple.open();
}}
/>
];
}, [table.hasSelectedRecords]);
const rowActions = useCallback((record: any) => {
return [
{
icon: <IconCirclePlus />,
color: 'green',
title: t`Add Test Result`,
onClick: (event: any) => {
setSelectedOutput(record.pk);
setSelectedTemplate(undefined);
createTestResult.open();
}
}
];
}, []);
return (
<>
{createTestResult.modal}
{createTestResultMultiple.modal}
<InvenTreeTable
url={apiUrl(ApiEndpoints.stock_item_list)}
tableState={table}
@@ -241,6 +319,8 @@ export default function BuildOrderTestTable({
tests: true,
build: buildId
},
enableSelection: true,
rowActions: rowActions,
tableFilters: tableFilters,
tableActions: tableActions,
modelType: ModelType.stockitem

View File

@@ -79,11 +79,14 @@ test('Build Order - Basic Tests', async ({ browser }) => {
await loadTab(page, 'Test Results');
await page.getByText('Quantity: 25').waitFor();
await page.getByText('Continuity Checks').waitFor();
await page
const button = await page
.getByRole('row', { name: 'Quantity: 16' })
.getByRole('button')
.hover();
await page.getByText('Add Test Result').waitFor();
.getByLabel('add-test-result');
await button.click();
await page.getByRole('textbox', { name: 'text-field-value' }).waitFor();
await page.getByRole('button', { name: 'Cancel' }).click();
// Click through to the "parent" build
await loadTab(page, 'Build Details');