mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-19 05:25:42 +00:00
User defined states (#7862)
* Add custom user defined states * make tests more reliable * fix list options * Adapt version * do not engage if rebuilding * remove unneeded attr * remove unneeded attr * fix enum imports * adapt cove target * Add status_custom_key to all other serializers * fix serializer method * simplify branching * remove unneeded imports * inherit read_only status from leader field * Add more tests * fix tests * add test for function * refactor for easier testing * move test to seperate class * Add options testing * extend serializer * add test for all states and refactor to reuse already build functions * use custom field in PUI too * reset diff * style fix * fix comparison * Add test for str * test color exceptions too * remove user state from tracking * Add intro from model fields too * update docs * simplify implementation * update tests * fix name * rename test * simplify tags and test fully * extend test to machine status * move logic for response formatting over * extend api response with machine status * ensure only direct subclasses are discovered * test for length of total respone too * use new fields on PUI too * fix test assertion with plugins enabled * also observe rendering in filters * Add managment endpoints and APIs * Add contenttypes to PUI renderes * use filteres instead * fix import order * fix api route definition * move status choices to serializer * fix lookup * fix filtering * remove admin integration * cleanup migration * fix migration change * cleanup code location * fix imports * Add docs for custom states * add links to custom status
This commit is contained in:
@ -19,7 +19,7 @@ export function RenderBuildOrder(
|
||||
primary={instance.reference}
|
||||
secondary={instance.title}
|
||||
suffix={StatusRenderer({
|
||||
status: instance.status,
|
||||
status: instance.status_custom_key,
|
||||
type: ModelType.build
|
||||
})}
|
||||
image={instance.part_detail?.thumbnail || instance.part_detail?.image}
|
||||
@ -39,7 +39,7 @@ export function RenderBuildLine({
|
||||
primary={instance.part_detail.full_name}
|
||||
secondary={instance.quantity}
|
||||
suffix={StatusRenderer({
|
||||
status: instance.status,
|
||||
status: instance.status_custom_key,
|
||||
type: ModelType.build
|
||||
})}
|
||||
image={instance.part_detail.thumbnail || instance.part_detail.image}
|
||||
|
@ -15,6 +15,12 @@ export function RenderProjectCode({
|
||||
);
|
||||
}
|
||||
|
||||
export function RenderContentType({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
return instance && <RenderInlineModel primary={instance.app_labeled_name} />;
|
||||
}
|
||||
|
||||
export function RenderImportSession({
|
||||
instance
|
||||
}: {
|
||||
|
@ -16,7 +16,11 @@ import {
|
||||
RenderManufacturerPart,
|
||||
RenderSupplierPart
|
||||
} from './Company';
|
||||
import { RenderImportSession, RenderProjectCode } from './Generic';
|
||||
import {
|
||||
RenderContentType,
|
||||
RenderImportSession,
|
||||
RenderProjectCode
|
||||
} from './Generic';
|
||||
import { ModelInformationDict } from './ModelType';
|
||||
import {
|
||||
RenderPurchaseOrder,
|
||||
@ -87,7 +91,8 @@ const RendererLookup: EnumDictionary<
|
||||
[ModelType.importsession]: RenderImportSession,
|
||||
[ModelType.reporttemplate]: RenderReportTemplate,
|
||||
[ModelType.labeltemplate]: RenderLabelTemplate,
|
||||
[ModelType.pluginconfig]: RenderPlugin
|
||||
[ModelType.pluginconfig]: RenderPlugin,
|
||||
[ModelType.contenttype]: RenderContentType
|
||||
};
|
||||
|
||||
export type RenderInstanceProps = {
|
||||
|
@ -241,6 +241,11 @@ export const ModelInformationDict: ModelDict = {
|
||||
url_overview: '/pluginconfig',
|
||||
url_detail: '/pluginconfig/:pk/',
|
||||
api_endpoint: ApiEndpoints.plugin_list
|
||||
},
|
||||
contenttype: {
|
||||
label: t`Content Type`,
|
||||
label_multiple: t`Content Types`,
|
||||
api_endpoint: ApiEndpoints.content_type_list
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -21,7 +21,7 @@ export function RenderPurchaseOrder(
|
||||
primary={instance.reference}
|
||||
secondary={instance.description}
|
||||
suffix={StatusRenderer({
|
||||
status: instance.status,
|
||||
status: instance.status_custom_key,
|
||||
type: ModelType.purchaseorder
|
||||
})}
|
||||
image={supplier.thumnbnail || supplier.image}
|
||||
@ -49,7 +49,7 @@ export function RenderReturnOrder(
|
||||
primary={instance.reference}
|
||||
secondary={instance.description}
|
||||
suffix={StatusRenderer({
|
||||
status: instance.status,
|
||||
status: instance.status_custom_key,
|
||||
type: ModelType.returnorder
|
||||
})}
|
||||
image={customer.thumnbnail || customer.image}
|
||||
@ -94,7 +94,7 @@ export function RenderSalesOrder(
|
||||
primary={instance.reference}
|
||||
secondary={instance.description}
|
||||
suffix={StatusRenderer({
|
||||
status: instance.status,
|
||||
status: instance.status_custom_key,
|
||||
type: ModelType.salesorder
|
||||
})}
|
||||
image={customer.thumnbnail || customer.image}
|
||||
|
@ -42,11 +42,13 @@ export enum ApiEndpoints {
|
||||
generate_barcode = 'barcode/generate/',
|
||||
news = 'news/',
|
||||
global_status = 'generic/status/',
|
||||
custom_state_list = 'generic/status/custom/',
|
||||
version = 'version/',
|
||||
license = 'license/',
|
||||
sso_providers = 'auth/providers/',
|
||||
group_list = 'user/group/',
|
||||
owner_list = 'user/owner/',
|
||||
content_type_list = 'contenttype/',
|
||||
icons = 'icons/',
|
||||
|
||||
// Data import endpoints
|
||||
|
@ -31,5 +31,6 @@ export enum ModelType {
|
||||
group = 'group',
|
||||
reporttemplate = 'reporttemplate',
|
||||
labeltemplate = 'labeltemplate',
|
||||
pluginconfig = 'pluginconfig'
|
||||
pluginconfig = 'pluginconfig',
|
||||
contenttype = 'contenttype'
|
||||
}
|
||||
|
@ -301,7 +301,7 @@ export function useCompleteBuildOutputsForm({
|
||||
};
|
||||
})
|
||||
},
|
||||
status: {},
|
||||
status_custom_key: {},
|
||||
location: {
|
||||
filters: {
|
||||
structural: false
|
||||
|
@ -12,6 +12,18 @@ export function projectCodeFields(): ApiFormFieldSet {
|
||||
};
|
||||
}
|
||||
|
||||
export function customStateFields(): ApiFormFieldSet {
|
||||
return {
|
||||
key: {},
|
||||
name: {},
|
||||
label: {},
|
||||
color: {},
|
||||
logical_key: {},
|
||||
model: {},
|
||||
reference_status: {}
|
||||
};
|
||||
}
|
||||
|
||||
export function customUnitsFields(): ApiFormFieldSet {
|
||||
return {
|
||||
name: {},
|
||||
|
@ -138,7 +138,7 @@ export function useStockFields({
|
||||
value: batchCode,
|
||||
onValueChange: (value) => setBatchCode(value)
|
||||
},
|
||||
status: {},
|
||||
status_custom_key: {},
|
||||
expiry_date: {
|
||||
// TODO: icon
|
||||
},
|
||||
@ -620,7 +620,7 @@ function stockChangeStatusFields(items: any[]): ApiFormFieldSet {
|
||||
},
|
||||
headers: [t`Part`, t`Location`, t`In Stock`, t`Actions`]
|
||||
},
|
||||
status: {},
|
||||
status_custom_key: {},
|
||||
note: {}
|
||||
};
|
||||
|
||||
|
@ -68,6 +68,10 @@ const ProjectCodeTable = Loadable(
|
||||
lazy(() => import('../../../../tables/settings/ProjectCodeTable'))
|
||||
);
|
||||
|
||||
const CustomStateTable = Loadable(
|
||||
lazy(() => import('../../../../tables/settings/CustomStateTable'))
|
||||
);
|
||||
|
||||
const CustomUnitsTable = Loadable(
|
||||
lazy(() => import('../../../../tables/settings/CustomUnitsTable'))
|
||||
);
|
||||
@ -135,6 +139,12 @@ export default function AdminCenter() {
|
||||
</Stack>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'customstates',
|
||||
label: t`Custom States`,
|
||||
icon: <IconListDetails />,
|
||||
content: <CustomStateTable />
|
||||
},
|
||||
{
|
||||
name: 'customunits',
|
||||
label: t`Custom Units`,
|
||||
|
@ -526,7 +526,7 @@ export default function BuildDetail() {
|
||||
? []
|
||||
: [
|
||||
<StatusRenderer
|
||||
status={build.status}
|
||||
status={build.status_custom_key}
|
||||
type={ModelType.build}
|
||||
options={{ size: 'lg' }}
|
||||
/>
|
||||
|
@ -459,7 +459,7 @@ export default function PurchaseOrderDetail() {
|
||||
? []
|
||||
: [
|
||||
<StatusRenderer
|
||||
status={order.status}
|
||||
status={order.status_custom_key}
|
||||
type={ModelType.purchaseorder}
|
||||
options={{ size: 'lg' }}
|
||||
/>
|
||||
|
@ -300,7 +300,7 @@ export default function ReturnOrderDetail() {
|
||||
? []
|
||||
: [
|
||||
<StatusRenderer
|
||||
status={order.status}
|
||||
status={order.status_custom_key}
|
||||
type={ModelType.returnorder}
|
||||
options={{ size: 'lg' }}
|
||||
/>
|
||||
|
@ -498,7 +498,7 @@ export default function SalesOrderDetail() {
|
||||
? []
|
||||
: [
|
||||
<StatusRenderer
|
||||
status={order.status}
|
||||
status={order.status_custom_key}
|
||||
type={ModelType.salesorder}
|
||||
options={{ size: 'lg' }}
|
||||
key={order.pk}
|
||||
|
@ -601,7 +601,7 @@ export default function StockDetail() {
|
||||
key="batch"
|
||||
/>,
|
||||
<StatusRenderer
|
||||
status={stockitem.status}
|
||||
status={stockitem.status_custom_key}
|
||||
type={ModelType.stockitem}
|
||||
options={{ size: 'lg' }}
|
||||
key="status"
|
||||
|
@ -178,7 +178,7 @@ export function StatusColumn({
|
||||
sortable: sortable ?? true,
|
||||
title: title,
|
||||
hidden: hidden,
|
||||
render: TableStatusRenderer(model, accessor ?? 'status')
|
||||
render: TableStatusRenderer(model, accessor ?? 'status_custom_key')
|
||||
};
|
||||
}
|
||||
|
||||
|
137
src/frontend/src/tables/settings/CustomStateTable.tsx
Normal file
137
src/frontend/src/tables/settings/CustomStateTable.tsx
Normal file
@ -0,0 +1,137 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { customStateFields } from '../../forms/CommonForms';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
||||
|
||||
/**
|
||||
* Table for displaying list of custom states
|
||||
*/
|
||||
export default function CustomStateTable() {
|
||||
const table = useTable('customstates');
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
const columns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
accessor: 'name',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
accessor: 'label',
|
||||
title: t`Display Name`,
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
accessor: 'color'
|
||||
},
|
||||
{
|
||||
accessor: 'key',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
accessor: 'logical_key',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
accessor: 'model_name',
|
||||
title: t`Model`,
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
accessor: 'reference_status',
|
||||
title: t`Status`,
|
||||
sortable: true
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
const newCustomState = useCreateApiFormModal({
|
||||
url: ApiEndpoints.custom_state_list,
|
||||
title: t`Add State`,
|
||||
fields: customStateFields(),
|
||||
table: table
|
||||
});
|
||||
|
||||
const [selectedCustomState, setSelectedCustomState] = useState<
|
||||
number | undefined
|
||||
>(undefined);
|
||||
|
||||
const editCustomState = useEditApiFormModal({
|
||||
url: ApiEndpoints.custom_state_list,
|
||||
pk: selectedCustomState,
|
||||
title: t`Edit State`,
|
||||
fields: customStateFields(),
|
||||
table: table
|
||||
});
|
||||
|
||||
const deleteCustomState = useDeleteApiFormModal({
|
||||
url: ApiEndpoints.custom_state_list,
|
||||
pk: selectedCustomState,
|
||||
title: t`Delete State`,
|
||||
table: table
|
||||
});
|
||||
|
||||
const rowActions = useCallback(
|
||||
(record: any): RowAction[] => {
|
||||
return [
|
||||
RowEditAction({
|
||||
hidden: !user.hasChangeRole(UserRoles.admin),
|
||||
onClick: () => {
|
||||
setSelectedCustomState(record.pk);
|
||||
editCustomState.open();
|
||||
}
|
||||
}),
|
||||
RowDeleteAction({
|
||||
hidden: !user.hasDeleteRole(UserRoles.admin),
|
||||
onClick: () => {
|
||||
setSelectedCustomState(record.pk);
|
||||
deleteCustomState.open();
|
||||
}
|
||||
})
|
||||
];
|
||||
},
|
||||
[user]
|
||||
);
|
||||
|
||||
const tableActions = useMemo(() => {
|
||||
return [
|
||||
<AddItemButton
|
||||
onClick={() => newCustomState.open()}
|
||||
tooltip={t`Add state`}
|
||||
/>
|
||||
];
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{newCustomState.modal}
|
||||
{editCustomState.modal}
|
||||
{deleteCustomState.modal}
|
||||
<InvenTreeTable
|
||||
url={apiUrl(ApiEndpoints.custom_state_list)}
|
||||
tableState={table}
|
||||
columns={columns}
|
||||
props={{
|
||||
rowActions: rowActions,
|
||||
tableActions: tableActions,
|
||||
enableDownload: true
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user