feat: approval, carbon copy (#1330)
Co-authored-by: wjh <wwwjh0710@163.com> Co-authored-by: sealday <zhanglin@daoyoucloud.com> Reviewed-on: daoyoucloud/tachybase#1330 Co-authored-by: bai.zixv <bai.zixv@foxmail.com> Co-committed-by: bai.zixv <bai.zixv@foxmail.com>
This commit is contained in:
parent
182d409c79
commit
f28a45cf7e
@ -0,0 +1,187 @@
|
|||||||
|
import { COLLECTION_NAME_APPROVAL_CARBON_COPY } from '../../common/constants';
|
||||||
|
import { ApprovalStatusEnums } from '../constants';
|
||||||
|
import { NAMESPACE, tval } from '../locale';
|
||||||
|
import { approvalStatusOptions } from '../usage/approval-common/notice-columns/column.status';
|
||||||
|
|
||||||
|
export const CollectionApprovalCarbonCopy = {
|
||||||
|
title: tval('ApprovalCarbonCopy'),
|
||||||
|
name: COLLECTION_NAME_APPROVAL_CARBON_COPY,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'bigInt',
|
||||||
|
name: 'approvalId',
|
||||||
|
interface: 'number',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: 'ID',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'createdBy',
|
||||||
|
target: 'users',
|
||||||
|
foreignKey: 'createdById',
|
||||||
|
interface: 'm2o',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: `{{t("Initiator", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-component': 'RemoteSelect',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
label: 'nickname',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
service: {
|
||||||
|
resource: 'users',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'bigInt',
|
||||||
|
name: 'id',
|
||||||
|
interface: 'number',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: 'ID',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'user',
|
||||||
|
target: 'users',
|
||||||
|
foreignKey: 'userId',
|
||||||
|
interface: 'm2o',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: tval('The Notified Person'),
|
||||||
|
'x-component': 'RemoteSelect',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
label: 'nickname',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
service: { resource: 'users' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'node',
|
||||||
|
target: 'flow_nodes',
|
||||||
|
foreignKey: 'nodeId',
|
||||||
|
interface: 'm2o',
|
||||||
|
isAssociation: true,
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-component': 'RemoteSelect',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
label: 'title',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
service: {
|
||||||
|
resource: 'flow_nodes',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'workflow',
|
||||||
|
target: 'workflows',
|
||||||
|
foreignKey: 'workflowId',
|
||||||
|
interface: 'm2o',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: '{{t("Workflow", { ns: "workflow" })}}',
|
||||||
|
'x-component': 'RemoteSelect',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
label: 'title',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
service: { resource: 'workflows' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'integer',
|
||||||
|
name: 'status',
|
||||||
|
interface: 'select',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: '{{t("Status", { ns: "workflow" })}}',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: approvalStatusOptions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'comment',
|
||||||
|
interface: 'markdown',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Markdown',
|
||||||
|
title: `{{t("Comment", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'date',
|
||||||
|
interface: 'createdAt',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'datetime',
|
||||||
|
title: '{{t("Created at")}}',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {
|
||||||
|
showTime: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'date',
|
||||||
|
interface: 'updatedAt',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'datetime',
|
||||||
|
title: '{{t("Updated at")}}',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {
|
||||||
|
showTime: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'summary',
|
||||||
|
interface: 'input',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
title: tval('Summary'),
|
||||||
|
'x-component': 'ApprovalsSummary',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'integer',
|
||||||
|
name: 'status',
|
||||||
|
interface: 'select',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: '{{t("Status", { ns: "workflow" })}}',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: ApprovalStatusEnums,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
@ -1,5 +1,5 @@
|
|||||||
import { ApprovalStatusEnums } from '../../constants';
|
import { ApprovalStatusEnums } from '../constants';
|
||||||
import { NAMESPACE, tval } from '../../locale';
|
import { NAMESPACE, tval } from '../locale';
|
||||||
|
|
||||||
export const CollectionApprovals = {
|
export const CollectionApprovals = {
|
||||||
title: `{{t("Approval applications", { ns: "${NAMESPACE}" })}}`,
|
title: `{{t("Approval applications", { ns: "${NAMESPACE}" })}}`,
|
@ -1,7 +1,7 @@
|
|||||||
import { NAMESPACE } from '../../locale';
|
import { tval } from '../locale';
|
||||||
|
|
||||||
export const CollectionFlowNodes = {
|
export const CollectionFlowNodes = {
|
||||||
title: `{{t("Node", { ns: "${NAMESPACE}" })}}`,
|
title: tval('Node'),
|
||||||
name: 'flow_nodes',
|
name: 'flow_nodes',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
@ -1,4 +1,4 @@
|
|||||||
import { NAMESPACE } from '../../locale';
|
import { NAMESPACE } from '../locale';
|
||||||
|
|
||||||
export const CollectionWorkflows = {
|
export const CollectionWorkflows = {
|
||||||
title: `{{t("Workflow", { ns: "${NAMESPACE}" })}}`,
|
title: `{{t("Workflow", { ns: "${NAMESPACE}" })}}`,
|
@ -11,7 +11,7 @@ import { useForm } from '@tachybase/schema';
|
|||||||
import { uid } from '@tachybase/utils/client';
|
import { uid } from '@tachybase/utils/client';
|
||||||
|
|
||||||
import { useFlowContext } from '../../../../../FlowContext';
|
import { useFlowContext } from '../../../../../FlowContext';
|
||||||
import { CollectionApprovalTodos } from '../../../common/ApprovalTodos.collection';
|
import { CollectionApprovalTodos } from '../../../collections/approvalRecords.collection';
|
||||||
import { NAMESPACE } from '../../../locale';
|
import { NAMESPACE } from '../../../locale';
|
||||||
import { SchemaAddBlock } from '../../trigger/launcher-interface/SchemaAddBlock.component';
|
import { SchemaAddBlock } from '../../trigger/launcher-interface/SchemaAddBlock.component';
|
||||||
import { ApproverBlock } from './ApproverBlock.view';
|
import { ApproverBlock } from './ApproverBlock.view';
|
@ -0,0 +1,89 @@
|
|||||||
|
import { ArrayItems } from '@tachybase/components';
|
||||||
|
|
||||||
|
import { GroupType, Instruction } from '../../../../nodes';
|
||||||
|
import { COLLECTION_NAME_APPROVAL_CARBON_COPY } from '../../../common/constants';
|
||||||
|
import { tval } from '../../locale';
|
||||||
|
import { AdditionNotifiedPerson } from './config-items/AddNotifiedPerson.view';
|
||||||
|
import { ConfigButtonNotice } from './config-items/ConfigButtonNotice.view';
|
||||||
|
import { SelectNotifiedPerson } from './config-items/SelectNotifiedPerson.view';
|
||||||
|
import { CarbonCopyDetailContainer } from './show-interface/CarbonCopyDetailContainer.schema';
|
||||||
|
import { configSytles } from './style';
|
||||||
|
|
||||||
|
export class ApprovalCarbonCopyInstruction extends Instruction {
|
||||||
|
title = tval('ApprovalCarbonCopy');
|
||||||
|
type = COLLECTION_NAME_APPROVAL_CARBON_COPY;
|
||||||
|
group: GroupType = 'extended';
|
||||||
|
description = tval(
|
||||||
|
'In the workflow, notification messages can be viewed by the notified person in the notification center.',
|
||||||
|
);
|
||||||
|
components = {
|
||||||
|
ArrayItems: ArrayItems,
|
||||||
|
SelectNotifiedPerson: SelectNotifiedPerson,
|
||||||
|
AdditionNotifiedPerson: AdditionNotifiedPerson,
|
||||||
|
ConfigButtonNotice: ConfigButtonNotice,
|
||||||
|
CarbonCopyDetail: CarbonCopyDetailContainer,
|
||||||
|
};
|
||||||
|
fieldset = {
|
||||||
|
carbonCopyPerson: {
|
||||||
|
type: 'array',
|
||||||
|
title: tval('The Notified Person'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ArrayItems',
|
||||||
|
'x-component-props': {
|
||||||
|
className: configSytles.ArrayItemsStyle,
|
||||||
|
},
|
||||||
|
|
||||||
|
items: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
className: configSytles.SpaceStyle,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
sort: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ArrayItems.SortHandle',
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'SelectNotifiedPerson',
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ArrayItems.Remove',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
add: {
|
||||||
|
type: 'void',
|
||||||
|
title: tval('Add Notified Person'),
|
||||||
|
// HACK: 这里原本的框架逻辑, 解析竟然和 Addition 这个单词绑定, 奇怪
|
||||||
|
// 类似的还有, Index, Remove, MoveUp, MoveDown, Copy
|
||||||
|
// isOperationComponent() 针对这类操作组件, 做了特殊逻辑
|
||||||
|
'x-component': 'AdditionNotifiedPerson',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
showCarbonCopyDetail: {
|
||||||
|
type: 'void',
|
||||||
|
title: tval('Show Notice Detail'),
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ConfigButtonNotice',
|
||||||
|
properties: {
|
||||||
|
showCarbonCopyDetail: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'CarbonCopyDetail',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
isAvailable(params) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { ArrayItems } from '@tachybase/components';
|
||||||
|
|
||||||
|
import { Button, Popover, Space } from 'antd';
|
||||||
|
|
||||||
|
import { useWorkflowExecuted } from '../../../../../hooks';
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
|
||||||
|
export const AdditionNotifiedPerson = () => {
|
||||||
|
const isWorkflowExecuted = useWorkflowExecuted();
|
||||||
|
const array = ArrayItems.useArray() || { field: [] };
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const onSlecet = useCallback(() => {
|
||||||
|
array.field.push('');
|
||||||
|
setIsOpen(false);
|
||||||
|
}, [array.field]);
|
||||||
|
|
||||||
|
const onQuery = useCallback(() => {
|
||||||
|
array.field.push({ filter: {} });
|
||||||
|
setIsOpen(false);
|
||||||
|
}, [array.field]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
open={!isWorkflowExecuted && isOpen}
|
||||||
|
onOpenChange={setIsOpen}
|
||||||
|
content={<PopoverContent onSlecet={onSlecet} onQuery={onQuery} />}
|
||||||
|
>
|
||||||
|
<Button className="ant-formily-array-base-addition" type="dashed" block={true} disabled={isWorkflowExecuted}>
|
||||||
|
{t('Add Notified Person')}
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Child Component
|
||||||
|
const PopoverContent = ({ onSlecet, onQuery }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<Space direction="vertical" size="small">
|
||||||
|
<Button type="text" onClick={onSlecet}>
|
||||||
|
{t('Select Notified Person')}
|
||||||
|
</Button>
|
||||||
|
<Button type="text" onClick={onQuery}>
|
||||||
|
{t('Query Notified Person')}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { ActionContextProvider } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { Button } from 'antd';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
|
||||||
|
export const ConfigButtonNotice = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
const handleButtonClick = useCallback(() => {
|
||||||
|
setVisible(true);
|
||||||
|
}, [setVisible]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button type="primary" onClick={handleButtonClick} disabled={false}>
|
||||||
|
{t('Go to configure')}
|
||||||
|
</Button>
|
||||||
|
<ActionContextProvider value={{ visible, setVisible, formValueChanged: false }}>
|
||||||
|
{props.children}
|
||||||
|
</ActionContextProvider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,64 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { RemoteSelect, SchemaComponent, useCollectionFilterOptions, useToken, Variable } from '@tachybase/client';
|
||||||
|
import { useField } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { FilterDynamicComponent } from '../../../../../components';
|
||||||
|
import { useWorkflowVariableOptions } from '../../../../../variable';
|
||||||
|
|
||||||
|
// XXX: 可以将这个选择组件封装为一个通用组件, 放在 workflow 下提供使用
|
||||||
|
export const SelectNotifiedPerson = (props) => {
|
||||||
|
if (props.value && typeof props.value === 'object') {
|
||||||
|
return <SelectNotifiedPersonCustom {...props} />;
|
||||||
|
} else {
|
||||||
|
return <SelectNotifiedPersonNormal {...props} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SelectNotifiedPersonNormal = ({ value, onChange }) => {
|
||||||
|
const scope = useWorkflowVariableOptions({ types: [isUserKeyField] });
|
||||||
|
return (
|
||||||
|
<Variable.Input scope={scope} value={value} onChange={onChange}>
|
||||||
|
<RemoteSelect
|
||||||
|
fieldNames={{ label: 'nickname', value: 'id' }}
|
||||||
|
service={{ resource: 'users' }}
|
||||||
|
manual={false}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
</Variable.Input>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SelectNotifiedPersonCustom = () => {
|
||||||
|
const field = useField();
|
||||||
|
const currentFormFields = useCollectionFilterOptions('users');
|
||||||
|
const { token } = useToken();
|
||||||
|
return (
|
||||||
|
<div style={{ border: `1px dashed ${token.colorBorder}`, padding: token.paddingSM }}>
|
||||||
|
<SchemaComponent
|
||||||
|
basePath={field.address}
|
||||||
|
schema={{
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
filter: {
|
||||||
|
type: 'object',
|
||||||
|
'x-component': 'Filter',
|
||||||
|
'x-component-props': {
|
||||||
|
options: currentFormFields,
|
||||||
|
dynamicComponent: FilterDynamicComponent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function isUserKeyField(field) {
|
||||||
|
if (field.isForeignKey) {
|
||||||
|
return field.target === 'users';
|
||||||
|
} else {
|
||||||
|
return field.collectionName === 'users' && field.name === 'id';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
import { Plugin } from '@tachybase/client';
|
||||||
|
|
||||||
|
// import { ArrayItems } from '@tachybase/components';
|
||||||
|
|
||||||
|
import { PluginWorkflow } from '../../../../Plugin';
|
||||||
|
import { COLLECTION_NAME_APPROVAL_CARBON_COPY } from '../../../common/constants';
|
||||||
|
import { ApprovalCarbonCopyInstruction } from './ApprovalCarbonCopy.instruction';
|
||||||
|
import { SCCarbonCopyDetail } from './show-interface/CarbonCopyDetail.schema';
|
||||||
|
|
||||||
|
// NOTE: 试着用一种新的约束规则去声明文件属于哪种功能. 比如 kit.ts 导出的是单个 Plugin 下的功能集成
|
||||||
|
// 注册一个节点
|
||||||
|
export class KitCarbonCopy extends Plugin {
|
||||||
|
async afterAdd() {
|
||||||
|
await this.app.pm.add(SCCarbonCopyDetail);
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
const pluginWorkflow = this.app.pm.get(PluginWorkflow);
|
||||||
|
pluginWorkflow.registerInstruction(COLLECTION_NAME_APPROVAL_CARBON_COPY, ApprovalCarbonCopyInstruction);
|
||||||
|
|
||||||
|
// this.app.addComponents({
|
||||||
|
// ArrayItems,
|
||||||
|
// });
|
||||||
|
// this.app.schemaInitializerManager.add(ApproverActionConfigInitializer);
|
||||||
|
// this.app.schemaInitializerManager.add(ApproverAddBlockInitializer);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
createReadPrettyFormBlockSchema,
|
||||||
|
parseCollectionName,
|
||||||
|
SchemaInitializerItem,
|
||||||
|
useRecordCollectionDataSourceItems,
|
||||||
|
useSchemaInitializer,
|
||||||
|
useSchemaInitializerItem,
|
||||||
|
useSchemaTemplateManager,
|
||||||
|
} from '@tachybase/client';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { useFlowContext } from '../../../../../FlowContext';
|
||||||
|
|
||||||
|
export const CarbonCopyDetailAddBlock = () => {
|
||||||
|
const { workflow } = useFlowContext();
|
||||||
|
const [dataSourceName, collection] = parseCollectionName(workflow.config?.collection);
|
||||||
|
const itemConfig = useSchemaInitializerItem();
|
||||||
|
const { insert } = useSchemaInitializer();
|
||||||
|
const { getTemplateSchemaByMode } = useSchemaTemplateManager();
|
||||||
|
const items = useRecordCollectionDataSourceItems('FormItem');
|
||||||
|
const handleClick = async ({ item }) => {
|
||||||
|
const template = item.template ? await getTemplateSchemaByMode(item) : null;
|
||||||
|
const schema = createReadPrettyFormBlockSchema({
|
||||||
|
actionInitializers: null,
|
||||||
|
resource: collection,
|
||||||
|
collection,
|
||||||
|
dataSource: dataSourceName,
|
||||||
|
template,
|
||||||
|
settings: 'blockSettings:details',
|
||||||
|
});
|
||||||
|
|
||||||
|
const shcemaWithPatch = {};
|
||||||
|
|
||||||
|
// REVIEW: 删除这个属性的必要性, 需要再思考下
|
||||||
|
_.unset(schema, ['x-acl-action', 'x-acl-action-props']);
|
||||||
|
|
||||||
|
const [childSchemaKey] = Object.keys(schema.properties);
|
||||||
|
|
||||||
|
_.set(schema.properties[childSchemaKey], 'x-component-props.useProps', 'usePropsNoticeDetail');
|
||||||
|
|
||||||
|
insert(schema);
|
||||||
|
};
|
||||||
|
return <SchemaInitializerItem {...itemConfig} onClick={handleClick} items={items} />;
|
||||||
|
};
|
@ -0,0 +1,75 @@
|
|||||||
|
import { gridRowColWrap, SchemaInitializer, usePlugin } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { useFlowContext } from '../../../../../FlowContext';
|
||||||
|
import { useAvailableUpstreams, useNodeContext } from '../../../../../nodes';
|
||||||
|
import { PluginWorkflow } from '../../../../../Plugin';
|
||||||
|
import { useTrigger } from '../../../../../triggers';
|
||||||
|
import { tval } from '../../../locale';
|
||||||
|
import { CarbonCopyDetailAddBlock } from './CarbonCopyDetail.block';
|
||||||
|
|
||||||
|
export const CarbonCopyDetailInitializer = new SchemaInitializer({
|
||||||
|
name: 'CarbonCopyDetailInitializer',
|
||||||
|
wrap: gridRowColWrap,
|
||||||
|
title: "{{t('Add block')}}",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'notice',
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: tval('Notice blocks'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'detail',
|
||||||
|
type: 'item',
|
||||||
|
title: '{{t("Details")}}',
|
||||||
|
Component: CarbonCopyDetailAddBlock,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
|
name: 'dataBlocks',
|
||||||
|
title: '{{t("Data blocks")}}',
|
||||||
|
checkChildrenLength: true,
|
||||||
|
useChildren() {
|
||||||
|
const workflowPlugin = usePlugin(PluginWorkflow);
|
||||||
|
const { workflow } = useFlowContext();
|
||||||
|
const trigger = useTrigger();
|
||||||
|
const currentNodeContext = useNodeContext();
|
||||||
|
const nodes = useAvailableUpstreams(currentNodeContext);
|
||||||
|
const triggerInitializers = [trigger.useInitializers?.call(trigger, workflow.config)].filter(Boolean);
|
||||||
|
const nodeBlockInitializers = nodes
|
||||||
|
.map((node) => {
|
||||||
|
const instruction = workflowPlugin.instructions.get(node.type);
|
||||||
|
return instruction?.useInitializers?.call(instruction, node);
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
return [
|
||||||
|
...triggerInitializers,
|
||||||
|
...(nodeBlockInitializers.length
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: 'nodes',
|
||||||
|
type: 'subMenu',
|
||||||
|
title: '{{t("Node result", { ns: "workflow" })}}',
|
||||||
|
children: nodeBlockInitializers,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
].filter(Boolean);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'others',
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: '{{t("Other blocks")}}',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'markdown',
|
||||||
|
type: 'item',
|
||||||
|
title: '{{t("Markdown")}}',
|
||||||
|
component: 'MarkdownBlockInitializer',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
@ -0,0 +1,86 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Plugin, SchemaComponent, useAPIClient, useRequest } from '@tachybase/client';
|
||||||
|
import { useForm } from '@tachybase/schema';
|
||||||
|
import { uid } from '@tachybase/utils/client';
|
||||||
|
|
||||||
|
import { Spin } from 'antd';
|
||||||
|
|
||||||
|
import { CarbonCopyDetailInitializer } from './CarbonCopyDetail.initializer';
|
||||||
|
|
||||||
|
export class SCCarbonCopyDetail extends Plugin {
|
||||||
|
async load() {
|
||||||
|
this.app.schemaInitializerManager.add(CarbonCopyDetailInitializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNewSchema(id) {
|
||||||
|
return {
|
||||||
|
type: 'void',
|
||||||
|
name: id,
|
||||||
|
'x-uid': id,
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'CarbonCopyDetailInitializer',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CarbonCopyDetail = (props) => {
|
||||||
|
const { schemaData, loading, handleChange } = useAction(props);
|
||||||
|
if (loading) {
|
||||||
|
return <Spin />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
memoized={true}
|
||||||
|
scope={
|
||||||
|
{
|
||||||
|
// useSubmit: () => {
|
||||||
|
// return { run() {} };
|
||||||
|
// },
|
||||||
|
// useApprovalFormBlockProps,
|
||||||
|
// useDetailsBlockProps: useFormBlockContext,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
components={{}}
|
||||||
|
schema={schemaData as any}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function useAction(props) {
|
||||||
|
const { value: srcID, onChange } = props;
|
||||||
|
const apiClient = useAPIClient();
|
||||||
|
const { values, setValuesIn } = useForm();
|
||||||
|
const { data: schemaData, loading } = useRequest(async () => {
|
||||||
|
if (srcID) {
|
||||||
|
const remoteSchema = await fetchSchema(apiClient, srcID);
|
||||||
|
if (remoteSchema['x-uid'] === srcID) {
|
||||||
|
return remoteSchema;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const id = srcID ?? uid();
|
||||||
|
const newSchema = createNewSchema(id);
|
||||||
|
await instertSchema(apiClient, newSchema);
|
||||||
|
onChange(id);
|
||||||
|
return newSchema;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = (data) => {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
schemaData,
|
||||||
|
loading,
|
||||||
|
handleChange,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchSchema(apiClient, srcID): Promise<object> {
|
||||||
|
const { data: serviceData } = await apiClient.request({ url: `uiSchemas:getJsonSchema/${srcID}` });
|
||||||
|
|
||||||
|
const remoteSchema = serviceData.data ?? {};
|
||||||
|
return remoteSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function instertSchema(apiClient, schema): Promise<void> {
|
||||||
|
await apiClient.resource('uiSchemas').insert({ values: schema });
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import React, { useContext, useMemo, useState } from 'react';
|
||||||
|
import { SchemaComponent, SchemaComponentContext } from '@tachybase/client';
|
||||||
|
import { uid } from '@tachybase/utils/client';
|
||||||
|
|
||||||
|
import { useFlowContext } from '../../../../../FlowContext';
|
||||||
|
import { tval } from '../../../locale';
|
||||||
|
import { CarbonCopyDetail } from './CarbonCopyDetail.schema';
|
||||||
|
import { schemaStyle } from './style';
|
||||||
|
|
||||||
|
export const CarbonCopyDetailContainer = () => {
|
||||||
|
const context = useContext(SchemaComponentContext);
|
||||||
|
const [, setId] = useState(uid());
|
||||||
|
const { workflow } = useFlowContext();
|
||||||
|
|
||||||
|
const contextValue = useMemo(() => {
|
||||||
|
return {
|
||||||
|
...context,
|
||||||
|
refresh: () => setId(uid()),
|
||||||
|
designable: !workflow.executed,
|
||||||
|
};
|
||||||
|
}, [context, workflow.executed]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaComponentContext.Provider value={contextValue}>
|
||||||
|
<SchemaComponent components={{ CarbonCopyDetail: CarbonCopyDetail }} schema={schema} />
|
||||||
|
</SchemaComponentContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
name: 'drawer',
|
||||||
|
type: 'void',
|
||||||
|
title: tval('The interface of show notice detail'),
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-component-props': {
|
||||||
|
className: schemaStyle.ActionStyle,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
showCarbonCopyDetail: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'CarbonCopyDetail',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,9 @@
|
|||||||
|
import { css } from '@tachybase/client';
|
||||||
|
|
||||||
|
export const schemaStyle = {
|
||||||
|
ActionStyle: css`
|
||||||
|
.ant-drawer-body {
|
||||||
|
background: var(--nb-box-bg);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
import { createStyles, css } from '@tachybase/client';
|
||||||
|
|
||||||
|
export const useStyles = createStyles({});
|
||||||
|
|
||||||
|
export const configSytles = {
|
||||||
|
ArrayItemsStyle: css`
|
||||||
|
&[disabled] {
|
||||||
|
> .ant-formily-array-base-addition {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
> .ant-formily-array-items-item .ant-space-item:not(:nth-child(2)) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
SpaceStyle: css`
|
||||||
|
width: 100%;
|
||||||
|
&.ant-space.ant-space-horizontal {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
> .ant-space-item:nth-child(2) {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
import { Plugin } from '@tachybase/client';
|
import { Plugin } from '@tachybase/client';
|
||||||
|
|
||||||
import { KitApprovalInstruction } from './instruction/plugin';
|
import { KitApprovalInstruction } from './instruction-approval/kit';
|
||||||
import { KitApprovalTrigger } from './trigger/plugin';
|
import { KitApprovalTrigger } from './trigger/plugin';
|
||||||
|
|
||||||
export default class KitApprovalConfiguration extends Plugin {
|
export default class KitApprovalConfiguration extends Plugin {
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { Plugin } from '@tachybase/client';
|
import { Plugin } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { KitCarbonCopy } from './configuration/instruction-carbon-copy/kit';
|
||||||
import KitApprovalConfiguration from './configuration/plugin';
|
import KitApprovalConfiguration from './configuration/plugin';
|
||||||
import { KitApprovalUsage } from './usage/plugin';
|
import { KitApprovalUsage } from './usage/plugin';
|
||||||
|
|
||||||
export class PluginApproval extends Plugin {
|
export class PluginApproval extends Plugin {
|
||||||
async afterAdd() {
|
async afterAdd() {
|
||||||
this.pm.add(KitApprovalConfiguration);
|
this.pm.add(KitApprovalConfiguration);
|
||||||
|
this.pm.add(KitCarbonCopy);
|
||||||
this.pm.add(KitApprovalUsage);
|
this.pm.add(KitApprovalUsage);
|
||||||
}
|
}
|
||||||
async beforeLoad() {}
|
async beforeLoad() {}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ExtendCollectionsProvider, TableBlockProvider, useRecord } from '@tachybase/client';
|
import { ExtendCollectionsProvider, TableBlockProvider, useRecord } from '@tachybase/client';
|
||||||
|
|
||||||
import { CollectionApprovalTodos } from '../../common/ApprovalTodos.collection';
|
import { CollectionApprovalTodos } from '../../collections/approvalRecords.collection';
|
||||||
import { CollectionApprovals } from '../approval-common/Approvals.collection';
|
import { CollectionApprovals } from '../../collections/approvals.collection';
|
||||||
import { CollectionFlowNodes } from '../approval-common/FlowNodes.collection';
|
import { CollectionFlowNodes } from '../../collections/flowNodes.collection';
|
||||||
import { CollectionWorkflows } from '../approval-common/Workflows.collection';
|
import { CollectionWorkflows } from '../../collections/workflows.collection';
|
||||||
|
|
||||||
export const ApprovalBlockProvider = ({ children, ...props }) => {
|
export const ApprovalBlockProvider = ({ children, ...props }) => {
|
||||||
const {
|
const {
|
||||||
|
@ -4,8 +4,11 @@ import { uid } from '@tachybase/utils/client';
|
|||||||
|
|
||||||
import { TableOutlined } from '@ant-design/icons';
|
import { TableOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
import { COLLECTION_NAME_APPROVAL_CARBON_COPY } from '../../../common/constants';
|
||||||
import { NAMESPACE } from '../../locale';
|
import { NAMESPACE } from '../../locale';
|
||||||
import { ApprovalBlockProvider } from './ApprovalBlock.provider';
|
import { ApprovalBlockProvider } from './ApprovalBlock.provider';
|
||||||
|
import { CarbonCopyBlockProvider } from './carbon-copy/CarbonCopyBlock.provider';
|
||||||
|
import { CarbonCopyCenter } from './carbon-copy/CarbonCopyCenter.schema';
|
||||||
import { ApprovalBlockLaunch } from './launch/VC.ApprovalBlockLaunch';
|
import { ApprovalBlockLaunch } from './launch/VC.ApprovalBlockLaunch';
|
||||||
import { ApprovalBlockLaunchApplication } from './launch/VC.ApprovalBlockLaunchApplication';
|
import { ApprovalBlockLaunchApplication } from './launch/VC.ApprovalBlockLaunchApplication';
|
||||||
import { ApprovalBlockTodos } from './todos/VC.ApprovalBlockTodos';
|
import { ApprovalBlockTodos } from './todos/VC.ApprovalBlockTodos';
|
||||||
@ -17,6 +20,8 @@ export class SCApprovalBlock extends Plugin {
|
|||||||
'ApprovalBlock.Launch': ApprovalBlockLaunch,
|
'ApprovalBlock.Launch': ApprovalBlockLaunch,
|
||||||
'ApprovalBlock.Launch.Application': ApprovalBlockLaunchApplication,
|
'ApprovalBlock.Launch.Application': ApprovalBlockLaunchApplication,
|
||||||
'ApprovalBlock.Todos': ApprovalBlockTodos,
|
'ApprovalBlock.Todos': ApprovalBlockTodos,
|
||||||
|
CarbonCopyBlockProvider: CarbonCopyBlockProvider,
|
||||||
|
CarbonCopyCenter: CarbonCopyCenter,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,23 +75,61 @@ const schemaItems = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
title: `{{t("CarbonCopy", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-decorator': 'CarbonCopyBlockProvider',
|
||||||
|
'x-component': 'CarbonCopyCenter',
|
||||||
|
'x-toolbar': 'BlockSchemaToolbar',
|
||||||
|
'x-settings': 'blockSettings:table',
|
||||||
|
collection: COLLECTION_NAME_APPROVAL_CARBON_COPY,
|
||||||
|
params: {
|
||||||
|
appends: [
|
||||||
|
'createdBy.id',
|
||||||
|
'createdBy.nickname',
|
||||||
|
'approval.status',
|
||||||
|
'user.id',
|
||||||
|
'user.nickname',
|
||||||
|
'node.id',
|
||||||
|
'node.title',
|
||||||
|
'job.id',
|
||||||
|
'job.status',
|
||||||
|
'job.result',
|
||||||
|
'workflow.id',
|
||||||
|
'workflow.title',
|
||||||
|
'workflow.enabled',
|
||||||
|
'execution.id',
|
||||||
|
'execution.status',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const getSchemaInsert = ({ item }) => {
|
const getSchemaInsert = ({ item }) => {
|
||||||
const id = uid();
|
const id = uid();
|
||||||
const { collection, params, ['x-component']: xcomponent, action } = item;
|
const {
|
||||||
|
collection,
|
||||||
|
params,
|
||||||
|
['x-component']: xcomponent,
|
||||||
|
action,
|
||||||
|
['x-decorator']: decorator,
|
||||||
|
['x-toolbar']: toolbar,
|
||||||
|
['x-settings']: settings,
|
||||||
|
} = item;
|
||||||
return {
|
return {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
name: id,
|
name: id,
|
||||||
'x-uid': id,
|
'x-uid': id,
|
||||||
'x-designer': 'TableBlockDesigner',
|
'x-designer': 'TableBlockDesigner',
|
||||||
'x-decorator': 'ApprovalBlock.Decorator',
|
'x-decorator': decorator || 'ApprovalBlock.Decorator',
|
||||||
'x-decorator-props': {
|
'x-decorator-props': {
|
||||||
collection,
|
collection,
|
||||||
params,
|
params,
|
||||||
action: action || 'listCentralized',
|
action: action || 'listCentralized',
|
||||||
},
|
},
|
||||||
'x-component': 'CardItem',
|
'x-component': 'CardItem',
|
||||||
|
'x-toolbar': toolbar,
|
||||||
|
'x-settings': settings,
|
||||||
properties: {
|
properties: {
|
||||||
block: {
|
block: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ExtendCollectionsProvider, TableBlockProvider, useRecord } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { CollectionApprovalCarbonCopy } from '../../../collections/approvalCarbonCopy.collection';
|
||||||
|
import { CollectionFlowNodes } from '../../../collections/flowNodes.collection';
|
||||||
|
import { CollectionWorkflows } from '../../../collections/workflows.collection';
|
||||||
|
|
||||||
|
export const CarbonCopyBlockProvider = ({ children, ...props }) => {
|
||||||
|
const {
|
||||||
|
collection,
|
||||||
|
params = {
|
||||||
|
filter: {},
|
||||||
|
},
|
||||||
|
action = 'list',
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const record = useRecord();
|
||||||
|
const config = {
|
||||||
|
collection,
|
||||||
|
resource: collection,
|
||||||
|
action,
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
sort: ['-createdAt'],
|
||||||
|
...params,
|
||||||
|
filter: record?.id
|
||||||
|
? {
|
||||||
|
dataKey: `${record.id}`,
|
||||||
|
...params.filter,
|
||||||
|
}
|
||||||
|
: { ...params.filter },
|
||||||
|
},
|
||||||
|
rowKey: 'id',
|
||||||
|
showIndex: true,
|
||||||
|
dragSort: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExtendCollectionsProvider collections={[CollectionWorkflows, CollectionFlowNodes, CollectionApprovalCarbonCopy]}>
|
||||||
|
<TableBlockProvider name={collection} {...config}>
|
||||||
|
{children}
|
||||||
|
</TableBlockProvider>
|
||||||
|
</ExtendCollectionsProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,217 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { css, SchemaComponent } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { NAMESPACE, tval } from '../../../locale';
|
||||||
|
import { ColumnApprovalStatus } from '../../approval-common/approval-columns/approvalStatus.column';
|
||||||
|
import { ColumnAction } from '../../approval-common/notice-columns/column.action';
|
||||||
|
import { ColumnNode } from '../../approval-common/notice-columns/column.node';
|
||||||
|
import { ColumnStatus } from '../../approval-common/notice-columns/column.status';
|
||||||
|
import { ColumnUser } from '../../approval-common/notice-columns/column.user';
|
||||||
|
import { ColumnWorkflow } from '../../approval-common/notice-columns/column.workflow';
|
||||||
|
|
||||||
|
const schemaStyles = {
|
||||||
|
ActionbarStyle: css`
|
||||||
|
margin-bottom: 16px;
|
||||||
|
`,
|
||||||
|
TableV2Style: css`
|
||||||
|
.ant-table-cell {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CarbonCopyCenter = () => (
|
||||||
|
<SchemaComponent
|
||||||
|
components={{
|
||||||
|
ColumnNode,
|
||||||
|
ColumnWorkflow,
|
||||||
|
ColumnUser,
|
||||||
|
ColumnStatus,
|
||||||
|
ColumnAction,
|
||||||
|
ColumnApprovalStatus,
|
||||||
|
}}
|
||||||
|
schema={schema}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
type: 'void',
|
||||||
|
name: 'approval-carbon-copy-center',
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
className: schemaStyles.ActionbarStyle,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
filter: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Filter") }}',
|
||||||
|
'x-action': 'filter',
|
||||||
|
'x-designer': 'Filter.Action.Designer',
|
||||||
|
'x-component': 'Filter.Action',
|
||||||
|
'x-use-component-props': 'useFilterActionProps',
|
||||||
|
'x-component-props': {
|
||||||
|
icon: 'FilterOutlined',
|
||||||
|
},
|
||||||
|
'x-align': 'left',
|
||||||
|
},
|
||||||
|
refresher: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Refresh") }}',
|
||||||
|
'x-action': 'refresh',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-use-component-props': 'useRefreshActionProps',
|
||||||
|
'x-designer': 'Action.Designer',
|
||||||
|
'x-component-props': { icon: 'ReloadOutlined' },
|
||||||
|
'x-align': 'right',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
type: 'array',
|
||||||
|
'x-component': 'TableV2',
|
||||||
|
'x-use-component-props': 'useTableBlockProps',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'id',
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
className: css`
|
||||||
|
.ant-table-cell {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
action: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
width: 60,
|
||||||
|
},
|
||||||
|
title: '{{t("Actions")}}',
|
||||||
|
properties: {
|
||||||
|
action: {
|
||||||
|
'x-component': 'ColumnAction',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
approvalId: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
title: tval('ID'),
|
||||||
|
properties: {
|
||||||
|
approvalId: {
|
||||||
|
type: 'number',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': { width: 160 },
|
||||||
|
properties: {
|
||||||
|
createdAt: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
summary: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
title: tval('Summary'),
|
||||||
|
properties: {
|
||||||
|
summary: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createdBy: {
|
||||||
|
type: 'void',
|
||||||
|
title: tval('Initiator'),
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': { width: 160 },
|
||||||
|
properties: {
|
||||||
|
createdBy: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'ColumnUser',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': { width: 140 },
|
||||||
|
title: tval('The Notified Person'),
|
||||||
|
properties: {
|
||||||
|
user: {
|
||||||
|
'x-component': 'ColumnUser',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
title: '{{t("Status", { ns: "workflow" })}}',
|
||||||
|
properties: {
|
||||||
|
status: {
|
||||||
|
'x-component': 'ColumnApprovalStatus',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
workflow: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
width: null,
|
||||||
|
},
|
||||||
|
title: '{{t("Workflow", { ns: "workflow" })}}',
|
||||||
|
properties: {
|
||||||
|
workflow: {
|
||||||
|
'x-component': 'ColumnWorkflow',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
width: null,
|
||||||
|
},
|
||||||
|
title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
properties: {
|
||||||
|
node: {
|
||||||
|
'x-component': 'ColumnNode',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -74,7 +74,7 @@ export const SchemaApprovalBlockLaunch = {
|
|||||||
'x-decorator': 'TableV2.Column.Decorator',
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
'x-component': 'TableV2.Column',
|
'x-component': 'TableV2.Column',
|
||||||
'x-component-props': { width: 80 },
|
'x-component-props': { width: 80 },
|
||||||
title: '{{t("ID")}}',
|
title: tval('ID'),
|
||||||
properties: {
|
properties: {
|
||||||
id: {
|
id: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SchemaComponent } from '@tachybase/client';
|
import { SchemaComponent } from '@tachybase/client';
|
||||||
|
|
||||||
import { NodeColumn } from '../../approval-common/approval-columns/column.node';
|
import { NodeColumn } from '../../approval-common/approval-columns/node.column';
|
||||||
import { UserColumn } from '../../approval-common/approval-columns/column.user';
|
import { UserColumn } from '../../approval-common/approval-columns/user.column';
|
||||||
import { WorkflowColumn } from '../../approval-common/approval-columns/column.workflow';
|
import { WorkflowColumn } from '../../approval-common/approval-columns/workflow.column';
|
||||||
import { ApplyButton } from './apply-button/VC.ApplyButton';
|
import { ApplyButton } from './apply-button/VC.ApplyButton';
|
||||||
import { SchemaApprovalBlockLaunch } from './Sm.ApprovalBlockLaunch';
|
import { SchemaApprovalBlockLaunch } from './Sm.ApprovalBlockLaunch';
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ export const ApprovalBlockLaunchApplication = (props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
run();
|
run();
|
||||||
}, []);
|
}, [decorator?.params?.filter]);
|
||||||
|
|
||||||
const onClick = useCallback(
|
const onClick = useCallback(
|
||||||
(targetItems) => {
|
(targetItems) => {
|
||||||
|
@ -66,7 +66,7 @@ export const SchemaApprovalBlockTodos = {
|
|||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
width: 80,
|
width: 80,
|
||||||
},
|
},
|
||||||
title: '{{t("ID")}}',
|
title: tval('ID'),
|
||||||
properties: {
|
properties: {
|
||||||
approvalId: {
|
approvalId: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SchemaComponent } from '@tachybase/client';
|
import { SchemaComponent } from '@tachybase/client';
|
||||||
|
|
||||||
import { NodeColumn } from '../../approval-common/approval-columns/column.node';
|
import { NodeColumn } from '../../approval-common/approval-columns/node.column';
|
||||||
import { ApprovalRecordStatusColumn } from '../../approval-common/approval-columns/column.status';
|
import { ApprovalRecordStatusColumn } from '../../approval-common/approval-columns/status.column';
|
||||||
import { UserColumn } from '../../approval-common/approval-columns/column.user';
|
import { UserColumn } from '../../approval-common/approval-columns/user.column';
|
||||||
import { WorkflowColumn } from '../../approval-common/approval-columns/column.workflow';
|
import { WorkflowColumn } from '../../approval-common/approval-columns/workflow.column';
|
||||||
import { SchemaApprovalBlockTodos } from './Sm.ApprovalBlockTodos';
|
import { SchemaApprovalBlockTodos } from './Sm.ApprovalBlockTodos';
|
||||||
|
|
||||||
// 审批-待办
|
// 审批-待办
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useCollectionRecordData, useCompile } from '@tachybase/client';
|
||||||
|
import { observer } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { Tag } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { ApprovalStatusEnumDict } from '../../../constants';
|
||||||
|
|
||||||
|
export const ColumnApprovalStatus = observer(
|
||||||
|
() => {
|
||||||
|
const record = useCollectionRecordData();
|
||||||
|
const { approval } = record;
|
||||||
|
return <ColumnStatusComponent value={approval?.status} record={record} />;
|
||||||
|
},
|
||||||
|
{ displayName: 'ColumnApprovalStatus' },
|
||||||
|
);
|
||||||
|
|
||||||
|
// 审批-待办:状态列:值
|
||||||
|
export const ColumnStatusComponent = (props) => {
|
||||||
|
const compile = useCompile();
|
||||||
|
const { value } = props;
|
||||||
|
const option = ApprovalStatusEnumDict[value];
|
||||||
|
|
||||||
|
return <Tag color={option.color}>{compile(option.label)}</Tag>;
|
||||||
|
};
|
@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SchemaComponent, useCollectionRecordData } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { NoticeDetailContent } from '../notice-show-detail/NoticeDetail.schema';
|
||||||
|
|
||||||
|
const createSchema = ({ record }) => {
|
||||||
|
const { id } = record;
|
||||||
|
return {
|
||||||
|
name: `notice-view-${id}`,
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Link',
|
||||||
|
title: '{{t("View")}}',
|
||||||
|
properties: {
|
||||||
|
drawer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-component-props': {
|
||||||
|
className: 'nb-action-popup',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
content: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'NoticeDetailContent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ColumnAction = () => {
|
||||||
|
const record = useCollectionRecordData();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
components={{
|
||||||
|
NoticeDetailContent: NoticeDetailContent,
|
||||||
|
}}
|
||||||
|
schema={createSchema({ record })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,17 @@
|
|||||||
|
import { Field, observer, useField } from '@tachybase/schema';
|
||||||
|
|
||||||
|
export const ColumnNode = observer(
|
||||||
|
() => {
|
||||||
|
const field = useField<Field>();
|
||||||
|
const fieldValue = field?.value;
|
||||||
|
|
||||||
|
if (fieldValue?.title) {
|
||||||
|
return fieldValue.title;
|
||||||
|
} else if (fieldValue?.id) {
|
||||||
|
return `#${fieldValue.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
{ displayName: 'ColumnNode' },
|
||||||
|
);
|
@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useCompile, useRecord } from '@tachybase/client';
|
||||||
|
import { Field, observer, useField } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { Tag } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { EXECUTION_STATUS } from '../../../../../constants';
|
||||||
|
import { NAMESPACE, useTranslation } from '../../../locale';
|
||||||
|
|
||||||
|
export const ColumnStatus = observer(
|
||||||
|
() => {
|
||||||
|
const { value } = useField<Field>();
|
||||||
|
const record = useRecord();
|
||||||
|
return <ColumnStatusComponent value={value} record={record} />;
|
||||||
|
},
|
||||||
|
{ displayName: 'ColumnStatus' },
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ColumnStatusComponent = (props) => {
|
||||||
|
const { value, record } = props;
|
||||||
|
const omitProps = _.omit(props, ['value', 'record']);
|
||||||
|
const compile = useCompile();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { option = approvalStatusConfigObj[value] } = omitProps;
|
||||||
|
const { workflow, execution, job } = record ?? {};
|
||||||
|
|
||||||
|
return (!workflow?.enabled || execution?.stauts || job?.status) && [].includes(value) ? (
|
||||||
|
<Tag>{t('Unprocessed')}</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag color={option?.color}>{compile(option?.label)}</Tag>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const approvalStatusOptions = [
|
||||||
|
{ value: EXECUTION_STATUS.QUEUEING, label: `{{t("Assigned", { ns: "${NAMESPACE}" })}}`, color: 'blue' },
|
||||||
|
{ value: EXECUTION_STATUS.STARTED, label: `{{t("Pending", { ns: "${NAMESPACE}" })}}`, color: 'gold' },
|
||||||
|
// { value: EXECUTION_STATUS.RETURNED, label: `{{t("Returned", { ns: "${NAMESPACE}" })}}`, color: 'purple' },
|
||||||
|
{ value: 2, label: `{{t("Approved", { ns: "${NAMESPACE}" })}}`, color: 'green' },
|
||||||
|
{ value: EXECUTION_STATUS.REJECTED, label: `{{t("Rejected", { ns: "${NAMESPACE}" })}}`, color: 'red' },
|
||||||
|
// { value: EXECUTION_STATUS.WITHDRAWN, label: `{{t("Withdrawn", { ns: "${NAMESPACE}" })}}` },
|
||||||
|
];
|
||||||
|
const approvalStatusConfigObj = approvalStatusOptions.reduce(
|
||||||
|
(e, t) =>
|
||||||
|
Object.assign(e, {
|
||||||
|
[t.value]: t,
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
@ -0,0 +1,16 @@
|
|||||||
|
import { Field, observer, useField } from '@tachybase/schema';
|
||||||
|
|
||||||
|
export const ColumnUser = observer(
|
||||||
|
() => {
|
||||||
|
const field = useField<Field>();
|
||||||
|
const fieldValue = field?.value;
|
||||||
|
|
||||||
|
if (fieldValue?.nickname) {
|
||||||
|
return fieldValue.nickname;
|
||||||
|
} else if (fieldValue?.id !== undefined) {
|
||||||
|
return fieldValue.id;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
{ displayName: 'ColumnUser' },
|
||||||
|
);
|
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { css } from '@tachybase/client';
|
||||||
|
import { Field, observer, useField } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
|
||||||
|
export const ColumnWorkflow = observer(
|
||||||
|
() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { value } = useField<Field>();
|
||||||
|
const title = value?.title || `#${value?.id}`;
|
||||||
|
|
||||||
|
if (value?.enabled) {
|
||||||
|
return title;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={css`
|
||||||
|
text-decoration: line-through;
|
||||||
|
`}
|
||||||
|
title={t('Disabled')}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ displayName: 'ColumnWorkflow' },
|
||||||
|
);
|
@ -0,0 +1,78 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
SchemaComponentContext,
|
||||||
|
useCollectionRecordData,
|
||||||
|
useRequest,
|
||||||
|
useSchemaComponentContext,
|
||||||
|
} from '@tachybase/client';
|
||||||
|
|
||||||
|
import { Result, Spin } from 'antd';
|
||||||
|
|
||||||
|
import { ExecutionContextProvider } from '../../../../../ExecutionContextProvider';
|
||||||
|
import { COLLECTION_NAME_APPROVAL_CARBON_COPY } from '../../../../common/constants';
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
import { ProviderContextMyComponent } from './contexts/MyComponent.context';
|
||||||
|
import { ProviderContextWorkflowNotice } from './contexts/WorkflowNotice.context';
|
||||||
|
|
||||||
|
export const NoticeDetailProvider = ({ children, ...props }) => {
|
||||||
|
// THINK: 顺序问题, 工具优先, 数据之后, 外部优先, 本地滞后
|
||||||
|
const schemaComponentContext = useSchemaComponentContext();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { id } = useCollectionRecordData();
|
||||||
|
// const { actionEnabled } = useContext(ContextWithActionEnabled);
|
||||||
|
const { loading, data } = useRequestData(id);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Spin />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data?.data) {
|
||||||
|
return <Result status="error" title={t('Submission may be withdrawn, please try refresh the list.')} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemsData = data.data;
|
||||||
|
const { node, workflow, execution } = itemsData;
|
||||||
|
const schemaId = node?.config.showCarbonCopyDetail;
|
||||||
|
const { designable } = props;
|
||||||
|
|
||||||
|
// THINK: Provider 的顺序, 数据放在外面, 配置放在内层
|
||||||
|
return (
|
||||||
|
<ExecutionContextProvider workflow={workflow} nodes={workflow.nodes} execution={execution}>
|
||||||
|
<ProviderContextWorkflowNotice value={itemsData}>
|
||||||
|
<ProviderContextMyComponent
|
||||||
|
value={{
|
||||||
|
id,
|
||||||
|
schemaId,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SchemaComponentContext.Provider value={{ ...schemaComponentContext, designable }}>
|
||||||
|
{children}
|
||||||
|
</SchemaComponentContext.Provider>
|
||||||
|
</ProviderContextMyComponent>
|
||||||
|
</ProviderContextWorkflowNotice>
|
||||||
|
</ExecutionContextProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function useRequestData(id) {
|
||||||
|
const { loading, data }: any = useRequest(
|
||||||
|
{
|
||||||
|
resource: COLLECTION_NAME_APPROVAL_CARBON_COPY,
|
||||||
|
action: 'get',
|
||||||
|
params: {
|
||||||
|
filterByTk: id,
|
||||||
|
appends: ['node', 'job', 'workflow', 'workflow.nodes', 'execution', 'execution.jobs', 'user'],
|
||||||
|
except: ['workflow.config', 'workflow.options', 'nodes.config'],
|
||||||
|
sort: ['-createdAt'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
refreshDeps: [id],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
RemoteSchemaComponent,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentProvider,
|
||||||
|
useFormBlockContext,
|
||||||
|
} from '@tachybase/client';
|
||||||
|
|
||||||
|
import { DetailsBlockProvider } from '../../../../../components';
|
||||||
|
import { tval } from '../../../locale';
|
||||||
|
import { useContextMyComponent } from './contexts/MyComponent.context';
|
||||||
|
import { usePropsNoticeDetail } from './hooks/usePropsNoticeDetail';
|
||||||
|
import { NoticeDetailProvider } from './NoticeDetail.provider';
|
||||||
|
|
||||||
|
export const NoticeDetailContent = () => {
|
||||||
|
return (
|
||||||
|
<NoticeDetailProvider>
|
||||||
|
<NoticeDetail />
|
||||||
|
</NoticeDetailProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NoticeDetail = (props) => {
|
||||||
|
const { id, schemaId } = useContextMyComponent();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
components={{
|
||||||
|
NoticeDetailProvider,
|
||||||
|
RemoteSchemaComponent,
|
||||||
|
SchemaComponentProvider,
|
||||||
|
DetailsBlockProvider,
|
||||||
|
}}
|
||||||
|
scope={{
|
||||||
|
usePropsNoticeDetail,
|
||||||
|
useDetailsBlockProps: useFormBlockContext,
|
||||||
|
}}
|
||||||
|
schema={{
|
||||||
|
name: `content-${id}`,
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Tabs',
|
||||||
|
properties: {
|
||||||
|
detail: {
|
||||||
|
type: 'void',
|
||||||
|
title: tval('Content Detail'),
|
||||||
|
'x-component': 'Tabs.TabPane',
|
||||||
|
properties: {
|
||||||
|
detail: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'NoticeDetailProvider',
|
||||||
|
'x-decorator-props': {
|
||||||
|
designable: false,
|
||||||
|
},
|
||||||
|
'x-component': 'RemoteSchemaComponent',
|
||||||
|
'x-component-props': {
|
||||||
|
uid: schemaId,
|
||||||
|
noForm: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface ContextMyComponentProps {
|
||||||
|
id?: string;
|
||||||
|
schemaId?: string;
|
||||||
|
}
|
||||||
|
export const ContextMyComponent = React.createContext<ContextMyComponentProps>({});
|
||||||
|
|
||||||
|
export const ProviderContextMyComponent = ContextMyComponent.Provider;
|
||||||
|
|
||||||
|
export function useContextMyComponent() {
|
||||||
|
return React.useContext(ContextMyComponent);
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
// NOTE: 传递的是内置表 workflowNotice 的数据
|
||||||
|
export const ContextWorkflowNotice = React.createContext<any>({});
|
||||||
|
|
||||||
|
export const ProviderContextWorkflowNotice = ContextWorkflowNotice.Provider;
|
||||||
|
|
||||||
|
export function useContexWorkflowNotice() {
|
||||||
|
return React.useContext(ContextWorkflowNotice);
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useFormBlockContext } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { useContexWorkflowNotice } from '../contexts/WorkflowNotice.context';
|
||||||
|
|
||||||
|
export function usePropsNoticeDetail() {
|
||||||
|
const { snapshot } = useContexWorkflowNotice();
|
||||||
|
const { form } = useFormBlockContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.setValues(snapshot);
|
||||||
|
}, [form, snapshot]);
|
||||||
|
|
||||||
|
return { form };
|
||||||
|
}
|
@ -4,7 +4,7 @@ import { useCompile } from '@tachybase/client';
|
|||||||
import { Tag } from 'antd';
|
import { Tag } from 'antd';
|
||||||
|
|
||||||
import { APPROVAL_STATUS, approvalStatusConfigObj, ApprovalStatusEnumDict } from '../../../constants';
|
import { APPROVAL_STATUS, approvalStatusConfigObj, ApprovalStatusEnumDict } from '../../../constants';
|
||||||
import { ColumnStatusComponent } from '../approval-columns/column.status';
|
import { ColumnStatusComponent } from '../approval-columns/status.column';
|
||||||
|
|
||||||
export function renderColumnStatus(value, record, exist) {
|
export function renderColumnStatus(value, record, exist) {
|
||||||
// return null;
|
// return null;
|
||||||
|
@ -8,10 +8,10 @@ import {
|
|||||||
useRecord,
|
useRecord,
|
||||||
} from '@tachybase/client';
|
} from '@tachybase/client';
|
||||||
|
|
||||||
import { CollectionApprovalTodos } from '../../common/ApprovalTodos.collection';
|
import { CollectionApprovalTodos } from '../../collections/approvalRecords.collection';
|
||||||
import { CollectionApprovals } from '../approval-common/Approvals.collection';
|
import { CollectionApprovals } from '../../collections/approvals.collection';
|
||||||
import { CollectionFlowNodes } from '../approval-common/FlowNodes.collection';
|
import { CollectionFlowNodes } from '../../collections/flowNodes.collection';
|
||||||
import { CollectionWorkflows } from '../approval-common/Workflows.collection';
|
import { CollectionWorkflows } from '../../collections/workflows.collection';
|
||||||
|
|
||||||
export function RecordApprovalsDecorator({ params, children }) {
|
export function RecordApprovalsDecorator({ params, children }) {
|
||||||
const collection = useCollection_deprecated();
|
const collection = useCollection_deprecated();
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SchemaComponent } from '@tachybase/client';
|
import { SchemaComponent } from '@tachybase/client';
|
||||||
|
|
||||||
import { ApprovalRecordStatusColumn } from '../approval-common/approval-columns/column.status';
|
import { ApprovalRecordStatusColumn } from '../approval-common/approval-columns/status.column';
|
||||||
import { UserColumn } from '../approval-common/approval-columns/column.user';
|
import { UserColumn } from '../approval-common/approval-columns/user.column';
|
||||||
import { WorkflowColumn } from '../approval-common/approval-columns/column.workflow';
|
import { WorkflowColumn } from '../approval-common/approval-columns/workflow.column';
|
||||||
import { SchemaRecordApprovals } from './RecordApprovals.schema';
|
import { SchemaRecordApprovals } from './RecordApprovals.schema';
|
||||||
|
|
||||||
// 视图组件,创建区块-相关审批
|
// 视图组件,创建区块-相关审批
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export const NOTICE_INSTRUCTION_NAMESPACE = 'notice';
|
export const NOTICE_INSTRUCTION_NAMESPACE = 'notice';
|
||||||
export const COLLECTION_NOTICE_NAME = 'workflowNotice';
|
export const COLLECTION_NOTICE_NAME = 'workflowNotice';
|
||||||
|
export const COLLECTION_NAME_APPROVAL_CARBON_COPY = 'approvalCarbonCopy';
|
||||||
|
@ -278,5 +278,7 @@
|
|||||||
"ms": "ms",
|
"ms": "ms",
|
||||||
"Resubmit": "Resubmit",
|
"Resubmit": "Resubmit",
|
||||||
"Are you sure you want to resubmit it?": "Are you sure you want to resubmit it?",
|
"Are you sure you want to resubmit it?": "Are you sure you want to resubmit it?",
|
||||||
"MyLaunch":"MyLaunch"
|
"MyLaunch":"MyLaunch",
|
||||||
|
"CarbonCopy":"CarbonCopy",
|
||||||
|
"ApprovalCarbonCopy":"Approval:CarbonCopy"
|
||||||
}
|
}
|
||||||
|
@ -359,5 +359,7 @@
|
|||||||
"ms": "毫秒",
|
"ms": "毫秒",
|
||||||
"Resubmit": "重新发起",
|
"Resubmit": "重新发起",
|
||||||
"Are you sure you want to resubmit it?": "是否确定重新发起?",
|
"Are you sure you want to resubmit it?": "是否确定重新发起?",
|
||||||
"MyLaunch":"我的发起"
|
"MyLaunch":"我的发起",
|
||||||
|
"CarbonCopy":"抄送",
|
||||||
|
"ApprovalCarbonCopy":"审批:抄送"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
import { UiSchemaRepository } from '@tachybase/plugin-ui-schema-storage';
|
||||||
|
|
||||||
|
import { JOB_STATUS } from '../../constants';
|
||||||
|
import Instruction from '../../instructions';
|
||||||
|
import { COLLECTION_NAME_APPROVAL_CARBON_COPY } from '../common/constants';
|
||||||
|
import { parsePerson } from './tools';
|
||||||
|
|
||||||
|
export default class ApprovalCarbonCopyInstruction extends Instruction {
|
||||||
|
async run(node, prevJob, processor) {
|
||||||
|
const job = await processor.saveJob({
|
||||||
|
status: JOB_STATUS.RESOLVED,
|
||||||
|
nodeId: node.id,
|
||||||
|
nodeKey: node.key,
|
||||||
|
upstreamId: prevJob?.id ?? null,
|
||||||
|
});
|
||||||
|
const targetPersonList = await parsePerson({ node, processor, keyName: 'carbonCopyPerson' });
|
||||||
|
if (targetPersonList && targetPersonList.length > 0) {
|
||||||
|
const { db } = processor.options.plugin;
|
||||||
|
const ApprovalRepo = db.getRepository('approvals');
|
||||||
|
const approval = await ApprovalRepo.findOne({
|
||||||
|
filter: {
|
||||||
|
'executions.id': processor.execution.id,
|
||||||
|
},
|
||||||
|
fields: ['id', 'status', 'data', 'summary', 'collectionName'],
|
||||||
|
appends: ['approvalExecutions', 'createdBy'],
|
||||||
|
except: ['data'],
|
||||||
|
});
|
||||||
|
const CarbonCopyModel = db.getModel(COLLECTION_NAME_APPROVAL_CARBON_COPY);
|
||||||
|
const notifiedPersonDataMap = targetPersonList.map((userId, index) => ({
|
||||||
|
userId,
|
||||||
|
jobId: job.id,
|
||||||
|
nodeId: node.id,
|
||||||
|
executionId: job.executionId,
|
||||||
|
workflowId: node.workflowId,
|
||||||
|
index,
|
||||||
|
// TODO: 怎么做到状态实时更新, 观察下审批发起的表的状态是怎么做的...
|
||||||
|
createdById: approval.createdBy?.id,
|
||||||
|
approvalId: approval.id,
|
||||||
|
status: approval.status,
|
||||||
|
snapshot: approval.data,
|
||||||
|
summary: approval.summary,
|
||||||
|
collectionName: approval.collectionName,
|
||||||
|
dataKey: approval.dataKey,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await CarbonCopyModel.bulkCreate(notifiedPersonDataMap, {
|
||||||
|
transaction: processor.transaction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
async duplicateConfig(node, { transaction }) {
|
||||||
|
const keyName = 'showCarbonCopyDetail';
|
||||||
|
const uiSchemaRepo = this.workflow.app.db.getRepository<UiSchemaRepository>('uiSchemas');
|
||||||
|
if (!node.config[keyName]) {
|
||||||
|
return node.config;
|
||||||
|
}
|
||||||
|
const resultSchema = await uiSchemaRepo.duplicate(node.config[keyName], {
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...node.config,
|
||||||
|
[keyName]: resultSchema?.['x-uid'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ import { parseCollectionName } from '@tachybase/data-source-manager';
|
|||||||
import { traverseJSON } from '@tachybase/database';
|
import { traverseJSON } from '@tachybase/database';
|
||||||
|
|
||||||
import { EXECUTION_STATUS, JOB_STATUS, PluginWorkflow } from '../..';
|
import { EXECUTION_STATUS, JOB_STATUS, PluginWorkflow } from '../..';
|
||||||
|
import { COLLECTION_NAME_APPROVAL_CARBON_COPY, COLLECTION_WORKFLOWS_NAME } from '../common/constants';
|
||||||
import { APPROVAL_ACTION_STATUS, APPROVAL_STATUS } from './constants';
|
import { APPROVAL_ACTION_STATUS, APPROVAL_STATUS } from './constants';
|
||||||
import { getSummary } from './tools';
|
import { getSummary } from './tools';
|
||||||
import { getAssociationName, jsonParse } from './utils';
|
import { getAssociationName, jsonParse } from './utils';
|
||||||
@ -357,6 +358,23 @@ const approvalRecords = {
|
|||||||
workflow.resume(approvalRecord.job);
|
workflow.resume(approvalRecord.job);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const approvalCarbonCopy = {
|
||||||
|
async listCentralized(context, next) {
|
||||||
|
const centralizedApprovalFlow = await context.db.getRepository(COLLECTION_WORKFLOWS_NAME).find({
|
||||||
|
filter: {
|
||||||
|
type: 'approval',
|
||||||
|
'config.centralized': true,
|
||||||
|
},
|
||||||
|
fields: ['id'],
|
||||||
|
});
|
||||||
|
context.action.mergeParams({
|
||||||
|
filter: {
|
||||||
|
workflowId: centralizedApprovalFlow.map((item) => item.id),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return actions.list(context, next);
|
||||||
|
},
|
||||||
|
};
|
||||||
function make(name, mod) {
|
function make(name, mod) {
|
||||||
return Object.keys(mod).reduce(
|
return Object.keys(mod).reduce(
|
||||||
(result, key) => ({
|
(result, key) => ({
|
||||||
@ -371,5 +389,6 @@ export function init({ app }) {
|
|||||||
...make('workflows', workflows),
|
...make('workflows', workflows),
|
||||||
...make('approvals', approvals),
|
...make('approvals', approvals),
|
||||||
...make('approvalRecords', approvalRecords),
|
...make('approvalRecords', approvalRecords),
|
||||||
|
...make(COLLECTION_NAME_APPROVAL_CARBON_COPY, approvalCarbonCopy),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
import { defineCollection } from '@tachybase/database';
|
||||||
|
|
||||||
|
import { COLLECTION_NAME_APPROVAL_CARBON_COPY } from '../../common/constants';
|
||||||
|
|
||||||
|
// 审批-抄送
|
||||||
|
export default defineCollection({
|
||||||
|
namespace: 'workflow.approvalCarbonCopy',
|
||||||
|
dumpRules: 'required',
|
||||||
|
name: COLLECTION_NAME_APPROVAL_CARBON_COPY,
|
||||||
|
createdBy: true,
|
||||||
|
updatedBy: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'collectionName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'index',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'dataKey',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'approval',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'integer',
|
||||||
|
name: 'status',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'user',
|
||||||
|
target: 'users',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'workflow',
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'execution',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'node',
|
||||||
|
target: 'flow_nodes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'job',
|
||||||
|
target: 'jobs',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'jsonb',
|
||||||
|
name: 'summary',
|
||||||
|
defaultValue: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'jsonb',
|
||||||
|
name: 'snapshot',
|
||||||
|
defaultValue: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
@ -1,6 +1,6 @@
|
|||||||
import { defineCollection } from '@tachybase/database';
|
import { defineCollection } from '@tachybase/database';
|
||||||
|
|
||||||
// 审批
|
// 审批-执行态
|
||||||
export default defineCollection({
|
export default defineCollection({
|
||||||
namespace: 'workflow.approvalExecutions',
|
namespace: 'workflow.approvalExecutions',
|
||||||
dumpRules: 'required',
|
dumpRules: 'required',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { defineCollection } from '@tachybase/database';
|
import { defineCollection } from '@tachybase/database';
|
||||||
|
|
||||||
// 审批待办
|
// 审批-待办
|
||||||
export default defineCollection({
|
export default defineCollection({
|
||||||
namespace: 'workflow.approvalRecords',
|
namespace: 'workflow.approvalRecords',
|
||||||
dumpRules: 'required',
|
dumpRules: 'required',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { defineCollection } from '@tachybase/database';
|
import { defineCollection } from '@tachybase/database';
|
||||||
|
|
||||||
// 审批发起
|
// 审批-发起
|
||||||
export default defineCollection({
|
export default defineCollection({
|
||||||
namespace: 'workflow.approvals',
|
namespace: 'workflow.approvals',
|
||||||
dumpRules: 'required',
|
dumpRules: 'required',
|
||||||
|
@ -2,7 +2,9 @@ import path from 'path';
|
|||||||
import { Plugin } from '@tachybase/server';
|
import { Plugin } from '@tachybase/server';
|
||||||
|
|
||||||
import { PluginWorkflow } from '../..';
|
import { PluginWorkflow } from '../..';
|
||||||
|
import { COLLECTION_NAME_APPROVAL_CARBON_COPY } from '../common/constants';
|
||||||
import { init } from './actions';
|
import { init } from './actions';
|
||||||
|
import ApprovalCarbonCopyInstruction from './ApprovalCarbonCopy.instruction';
|
||||||
import ApprovalInstruction from './ApprovalInstruction';
|
import ApprovalInstruction from './ApprovalInstruction';
|
||||||
import ApprovalTrigger from './ApprovalTrigger';
|
import ApprovalTrigger from './ApprovalTrigger';
|
||||||
|
|
||||||
@ -17,6 +19,7 @@ export class PluginWorkflowApproval extends Plugin {
|
|||||||
this.workflow = plugin;
|
this.workflow = plugin;
|
||||||
plugin.triggers.register('approval', new ApprovalTrigger(plugin));
|
plugin.triggers.register('approval', new ApprovalTrigger(plugin));
|
||||||
plugin.instructions.register('approval', new ApprovalInstruction(plugin));
|
plugin.instructions.register('approval', new ApprovalInstruction(plugin));
|
||||||
|
plugin.instructions.register(COLLECTION_NAME_APPROVAL_CARBON_COPY, new ApprovalCarbonCopyInstruction(plugin));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async load() {
|
async load() {
|
||||||
@ -36,6 +39,9 @@ export class PluginWorkflowApproval extends Plugin {
|
|||||||
this.app.acl.allow('approvals', '*', 'loggedIn');
|
this.app.acl.allow('approvals', '*', 'loggedIn');
|
||||||
this.app.acl.allow('approvalExecutions', ['get'], 'loggedIn');
|
this.app.acl.allow('approvalExecutions', ['get'], 'loggedIn');
|
||||||
this.app.acl.allow('approvalRecords', ['get', 'list', 'listCentralized', 'submit'], 'loggedIn');
|
this.app.acl.allow('approvalRecords', ['get', 'list', 'listCentralized', 'submit'], 'loggedIn');
|
||||||
|
// NOTE: 这种命名尽量改为引用同一个变量的形式,
|
||||||
|
// 这里因为不妨碍快速获得变量名, 所以没有保持风格一致, 直接使用推荐的方式
|
||||||
|
this.app.acl.allow(COLLECTION_NAME_APPROVAL_CARBON_COPY, ['get', 'list', 'listCentralized', 'submit'], 'loggedIn');
|
||||||
}
|
}
|
||||||
async install(options) {}
|
async install(options) {}
|
||||||
async afterEnable() {}
|
async afterEnable() {}
|
||||||
|
@ -19,3 +19,26 @@ export function getSummary(params: ParamsType): object {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function parsePerson({ node, processor, keyName }) {
|
||||||
|
const configPerson = processor
|
||||||
|
.getParsedValue(node.config?.[keyName] ?? [], node.id)
|
||||||
|
.flat()
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const targetPerson = new Set();
|
||||||
|
const UserRepo = processor.options.plugin.app.db.getRepository('users');
|
||||||
|
for (const item of configPerson) {
|
||||||
|
if (typeof item === 'object') {
|
||||||
|
const users = await UserRepo.find({
|
||||||
|
...item,
|
||||||
|
fields: ['id'],
|
||||||
|
transaction: processor.transaction,
|
||||||
|
});
|
||||||
|
users.forEach((userData) => targetPerson.add(userData.id));
|
||||||
|
} else {
|
||||||
|
targetPerson.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...targetPerson];
|
||||||
|
}
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
export const NOTICE_INSTRUCTION_NAMESPACE = 'notice';
|
export const NOTICE_INSTRUCTION_NAMESPACE = 'notice';
|
||||||
export const COLLECTION_NOTICE_NAME = 'workflowNotice';
|
export const COLLECTION_NOTICE_NAME = 'workflowNotice';
|
||||||
|
export const COLLECTION_NAME_APPROVAL_CARBON_COPY = 'approvalCarbonCopy';
|
||||||
|
export const COLLECTION_WORKFLOWS_NAME = 'workflows';
|
||||||
|
Loading…
Reference in New Issue
Block a user