mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-22 06:40:54 +00:00
Build start date (#8915)
* Add 'start_date' to Build model * Add to serializer * Add filtering and ordering * Update BuildOrderTable - Add new column - Add new filtering options * Add sanity check for start_date * Add 'start_date' field to BuildOrder form * Update docs * Bump API version * Tweak unit testing * Display 'start_date' on build page * Refactor UI tests * Fix for 'date' field in forms * Add additional unit tests * Fix helper func * Remove debug msg
This commit is contained in:
docs/docs/build
src
backend
InvenTree
frontend
src
components
forms
fields
forms
functions
pages
build
tables
build
tests
@ -22,8 +22,12 @@ export default function DateField({
|
||||
fieldState: { error }
|
||||
} = controller;
|
||||
|
||||
const valueFormat =
|
||||
definition.field_type == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss';
|
||||
const valueFormat = useMemo(() => {
|
||||
// Determine the format based on the field type
|
||||
return definition.field_type == 'date'
|
||||
? 'YYYY-MM-DD'
|
||||
: 'YYYY-MM-DD HH:mm:ss';
|
||||
}, [definition.field_type]);
|
||||
|
||||
const onChange = useCallback(
|
||||
(value: any) => {
|
||||
@ -31,12 +35,13 @@ export default function DateField({
|
||||
if (value) {
|
||||
value = value.toString();
|
||||
value = dayjs(value).format(valueFormat);
|
||||
value = value.toString().split('T')[0];
|
||||
}
|
||||
|
||||
field.onChange(value);
|
||||
definition.onValueChange?.(value);
|
||||
},
|
||||
[field.onChange, definition]
|
||||
[field.onChange, definition, valueFormat]
|
||||
);
|
||||
|
||||
const dateValue: Date | null = useMemo(() => {
|
||||
@ -62,7 +67,7 @@ export default function DateField({
|
||||
ref={field.ref}
|
||||
type={undefined}
|
||||
error={definition.error ?? error?.message}
|
||||
value={dateValue ?? null}
|
||||
value={dateValue}
|
||||
clearable={!definition.required}
|
||||
onChange={onChange}
|
||||
valueFormat={valueFormat}
|
||||
|
@ -101,6 +101,9 @@ export function useBuildOrderFields({
|
||||
value: batchCode,
|
||||
onValueChange: (value: any) => setBatchCode(value)
|
||||
},
|
||||
start_date: {
|
||||
icon: <IconCalendar />
|
||||
},
|
||||
target_date: {
|
||||
icon: <IconCalendar />
|
||||
},
|
||||
|
@ -139,12 +139,6 @@ export function constructField({
|
||||
};
|
||||
|
||||
switch (def.field_type) {
|
||||
case 'date':
|
||||
// Change value to a date object if required
|
||||
if (def.value) {
|
||||
def.value = new Date(def.value);
|
||||
}
|
||||
break;
|
||||
case 'nested object':
|
||||
def.children = {};
|
||||
for (const k of Object.keys(field.children ?? {})) {
|
||||
|
@ -181,21 +181,28 @@ export default function BuildDetail() {
|
||||
hidden: !build.responsible
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
type: 'date',
|
||||
name: 'creation_date',
|
||||
label: t`Created`,
|
||||
icon: 'calendar',
|
||||
hidden: !build.creation_date
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
type: 'date',
|
||||
name: 'start_date',
|
||||
label: t`Start Date`,
|
||||
icon: 'calendar',
|
||||
hidden: !build.start_date
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'target_date',
|
||||
label: t`Target Date`,
|
||||
icon: 'calendar',
|
||||
hidden: !build.target_date
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
type: 'date',
|
||||
name: 'completion_date',
|
||||
label: t`Completed`,
|
||||
icon: 'calendar',
|
||||
|
@ -105,6 +105,11 @@ export function BuildOrderTable({
|
||||
sortable: true
|
||||
},
|
||||
CreationDateColumn({}),
|
||||
DateColumn({
|
||||
accessor: 'start_date',
|
||||
title: t`Start Date`,
|
||||
sortable: true
|
||||
}),
|
||||
TargetDateColumn({}),
|
||||
DateColumn({
|
||||
accessor: 'completion_date',
|
||||
@ -138,6 +143,30 @@ export function BuildOrderTable({
|
||||
CreatedAfterFilter(),
|
||||
TargetDateBeforeFilter(),
|
||||
TargetDateAfterFilter(),
|
||||
{
|
||||
name: 'start_date_before',
|
||||
type: 'date',
|
||||
label: t`Start Date Before`,
|
||||
description: t`Show items with a start date before this date`
|
||||
},
|
||||
{
|
||||
name: 'start_date_after',
|
||||
type: 'date',
|
||||
label: t`Start Date After`,
|
||||
description: t`Show items with a start date after this date`
|
||||
},
|
||||
{
|
||||
name: 'has_target_date',
|
||||
type: 'boolean',
|
||||
label: t`Has Target Date`,
|
||||
description: t`Show orders with a target date`
|
||||
},
|
||||
{
|
||||
name: 'has_start_date',
|
||||
type: 'boolean',
|
||||
label: t`Has Start Date`,
|
||||
description: t`Show orders with a start date`
|
||||
},
|
||||
CompletedBeforeFilter(),
|
||||
CompletedAfterFilter(),
|
||||
ProjectCodeFilter({ choices: projectCodeFilters.choices }),
|
||||
|
@ -43,7 +43,11 @@ export const setTableChoiceFilter = async (page, filter, value) => {
|
||||
await page.getByRole('button', { name: 'Add Filter' }).click();
|
||||
await page.getByPlaceholder('Select filter').fill(filter);
|
||||
await page.getByPlaceholder('Select filter').click();
|
||||
await page.getByRole('option', { name: filter }).click();
|
||||
|
||||
// Construct a regex to match the filter name exactly
|
||||
const filterRegex = new RegExp(`^${filter}$`, 'i');
|
||||
|
||||
await page.getByRole('option', { name: filterRegex }).click();
|
||||
|
||||
await page.getByPlaceholder('Select filter value').click();
|
||||
await page.getByRole('option', { name: value }).click();
|
||||
|
@ -87,6 +87,33 @@ test('Build Order - Basic Tests', async ({ page }) => {
|
||||
.waitFor();
|
||||
});
|
||||
|
||||
test('Build Order - Edit', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
await page.goto(`${baseUrl}/manufacturing/build-order/22/`);
|
||||
|
||||
// Check for expected text items
|
||||
await page.getByText('Building for sales order').first().waitFor();
|
||||
await page.getByText('2024-08-08').waitFor(); // Created date
|
||||
await page.getByText('2025-01-01').waitFor(); // Start date
|
||||
await page.getByText('2025-01-22').waitFor(); // Target date
|
||||
|
||||
await page.keyboard.press('Control+E');
|
||||
|
||||
// Edit start date
|
||||
await page.getByLabel('date-field-start_date').fill('2026-09-09');
|
||||
|
||||
// Submit the form
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
// Expect error
|
||||
await page.getByText('Errors exist for one or more form fields').waitFor();
|
||||
await page.getByText('Target date must be after start date').waitFor();
|
||||
|
||||
// Cancel the form
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
|
||||
test('Build Order - Build Outputs', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
|
@ -1,51 +1,38 @@
|
||||
import { test } from './baseFixtures.js';
|
||||
import { baseUrl } from './defaults.js';
|
||||
import {
|
||||
clearTableFilters,
|
||||
closeFilterDrawer,
|
||||
openFilterDrawer
|
||||
} from './helpers.js';
|
||||
import { clearTableFilters, setTableChoiceFilter } from './helpers.js';
|
||||
import { doQuickLogin } from './login.js';
|
||||
|
||||
// Helper function to set the value of a specific table filter
|
||||
const setFilter = async (page, name: string, value: string) => {
|
||||
await openFilterDrawer(page);
|
||||
|
||||
await page.getByRole('button', { name: 'Add Filter' }).click();
|
||||
await page.getByPlaceholder('Select filter').click();
|
||||
await page.getByRole('option', { name: name, exact: true }).click();
|
||||
await page.getByPlaceholder('Select filter value').click();
|
||||
await page.getByRole('option', { name: value, exact: true }).click();
|
||||
|
||||
await closeFilterDrawer(page);
|
||||
};
|
||||
|
||||
test('Tables - Filters', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
// Head to the "build order list" page
|
||||
await page.goto(`${baseUrl}/manufacturing/index/`);
|
||||
|
||||
await setFilter(page, 'Status', 'Complete');
|
||||
await setFilter(page, 'Responsible', 'allaccess');
|
||||
await setFilter(page, 'Project Code', 'PRJ-NIM');
|
||||
await clearTableFilters(page);
|
||||
|
||||
await setTableChoiceFilter(page, 'Status', 'Complete');
|
||||
await setTableChoiceFilter(page, 'Responsible', 'allaccess');
|
||||
await setTableChoiceFilter(page, 'Project Code', 'PRJ-NIM');
|
||||
|
||||
await clearTableFilters(page);
|
||||
|
||||
// Head to the "part list" page
|
||||
await page.goto(`${baseUrl}/part/category/index/parts/`);
|
||||
|
||||
await setFilter(page, 'Assembly', 'Yes');
|
||||
await setTableChoiceFilter(page, 'Assembly', 'Yes');
|
||||
|
||||
await clearTableFilters(page);
|
||||
|
||||
// Head to the "purchase order list" page
|
||||
await page.goto(`${baseUrl}/purchasing/index/purchaseorders/`);
|
||||
|
||||
await setFilter(page, 'Status', 'Complete');
|
||||
await setFilter(page, 'Responsible', 'readers');
|
||||
await setFilter(page, 'Assigned to me', 'No');
|
||||
await setFilter(page, 'Project Code', 'PRO-ZEN');
|
||||
await clearTableFilters(page);
|
||||
|
||||
await setTableChoiceFilter(page, 'Status', 'Complete');
|
||||
await setTableChoiceFilter(page, 'Responsible', 'readers');
|
||||
await setTableChoiceFilter(page, 'Assigned to me', 'No');
|
||||
await setTableChoiceFilter(page, 'Project Code', 'PRO-ZEN');
|
||||
|
||||
await clearTableFilters(page);
|
||||
});
|
||||
|
Reference in New Issue
Block a user