2
0
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:
Matthias Mair
2024-08-21 23:33:15 +02:00
committed by GitHub
parent 0c63e509d2
commit d5086b2fb1
72 changed files with 1553 additions and 187 deletions

View File

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

View File

@ -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
}: {

View File

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

View File

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

View File

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

View File

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

View File

@ -31,5 +31,6 @@ export enum ModelType {
group = 'group',
reporttemplate = 'reporttemplate',
labeltemplate = 'labeltemplate',
pluginconfig = 'pluginconfig'
pluginconfig = 'pluginconfig',
contenttype = 'contenttype'
}

View File

@ -301,7 +301,7 @@ export function useCompleteBuildOutputsForm({
};
})
},
status: {},
status_custom_key: {},
location: {
filters: {
structural: false

View File

@ -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: {},

View File

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

View File

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

View File

@ -526,7 +526,7 @@ export default function BuildDetail() {
? []
: [
<StatusRenderer
status={build.status}
status={build.status_custom_key}
type={ModelType.build}
options={{ size: 'lg' }}
/>

View File

@ -459,7 +459,7 @@ export default function PurchaseOrderDetail() {
? []
: [
<StatusRenderer
status={order.status}
status={order.status_custom_key}
type={ModelType.purchaseorder}
options={{ size: 'lg' }}
/>

View File

@ -300,7 +300,7 @@ export default function ReturnOrderDetail() {
? []
: [
<StatusRenderer
status={order.status}
status={order.status_custom_key}
type={ModelType.returnorder}
options={{ size: 'lg' }}
/>

View File

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

View File

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

View File

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

View 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
}}
/>
</>
);
}