fix: approval, show process & feat: approval, lastNode (#1343)
Reviewed-on: daoyoucloud/tachybase#1343 Co-authored-by: bai.zixv <bai.zixv@foxmail.com> Co-committed-by: bai.zixv <bai.zixv@foxmail.com>
This commit is contained in:
parent
73fdc790f4
commit
220bf172fc
@ -60,7 +60,10 @@ export const CollectionApprovalTodos = {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`,
|
title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`,
|
||||||
'x-component': 'RemoteSelect',
|
'x-component': 'RemoteSelect',
|
||||||
'x-component-props': { fieldNames: { label: 'title', value: 'id' }, service: { resource: 'flow_nodes' } },
|
'x-component-props': {
|
||||||
|
fieldNames: { label: 'title', value: 'id' },
|
||||||
|
service: { resource: 'flow_nodes' },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -45,7 +45,14 @@ const schemaItems = [
|
|||||||
'x-component': 'ApprovalBlock.Launch',
|
'x-component': 'ApprovalBlock.Launch',
|
||||||
collection: 'approvals',
|
collection: 'approvals',
|
||||||
params: {
|
params: {
|
||||||
appends: ['createdBy.nickname', 'workflow.title', 'workflow.enabled'],
|
appends: [
|
||||||
|
'createdBy.nickname',
|
||||||
|
'workflow.title',
|
||||||
|
'workflow.enabled',
|
||||||
|
'records.id',
|
||||||
|
'records.status',
|
||||||
|
'records.node.title',
|
||||||
|
],
|
||||||
except: ['data'],
|
except: ['data'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -156,6 +156,21 @@ export const SchemaApprovalBlockLaunch = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
lastNode: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
properties: {
|
||||||
|
lastNode: {
|
||||||
|
'x-component': 'ApprovalLastNodeColumn',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -63,7 +63,7 @@ export const ViewActionLaunchContent = () => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const approvalData = data?.data;
|
const approvalData = data?.data;
|
||||||
|
|
||||||
const needHideProcess = actionEnabled || approvalData?.status !== APPROVAL_STATUS.RESUBMIT;
|
const needHideProcess = actionEnabled;
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Spin />;
|
return <Spin />;
|
||||||
|
@ -4,13 +4,27 @@ import { createStyles, useCurrentUserContext } from '@tachybase/client';
|
|||||||
import { Space, Table } from 'antd';
|
import { Space, Table } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { EXECUTION_STATUS } from '../../../../constants';
|
import { usePluginTranslation } from '../../locale';
|
||||||
import { APPROVAL_ACTION_STATUS, APPROVAL_STATUS } from '../../constants';
|
|
||||||
import { lang, usePluginTranslation } from '../../locale';
|
|
||||||
import { useApproval } from './ApprovalData.provider';
|
import { useApproval } from './ApprovalData.provider';
|
||||||
import { getAntdTableColumns } from './process-columns/columns';
|
import { getAntdTableColumns } from './process-columns';
|
||||||
|
import { getResults } from './tools';
|
||||||
import { ContextWithActionEnabled } from './WithActionEnabled.provider';
|
import { ContextWithActionEnabled } from './WithActionEnabled.provider';
|
||||||
|
|
||||||
|
const getStyles = createStyles(({ css, token }) => ({
|
||||||
|
layout: css`
|
||||||
|
display: flex;
|
||||||
|
`,
|
||||||
|
columnDetail: css`
|
||||||
|
.ant-description-textarea {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
time {
|
||||||
|
display: block;
|
||||||
|
color: ${token.colorTextTertiary};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
|
||||||
// 审批(发起/待办)区块-查看-审批处理
|
// 审批(发起/待办)区块-查看-审批处理
|
||||||
export const ApprovalProcess = (props) => {
|
export const ApprovalProcess = (props) => {
|
||||||
const { t } = usePluginTranslation();
|
const { t } = usePluginTranslation();
|
||||||
@ -32,88 +46,3 @@ export const ApprovalProcess = (props) => {
|
|||||||
</ContextWithActionEnabled.Provider>
|
</ContextWithActionEnabled.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStyles = createStyles(({ css, token }) => ({
|
|
||||||
layout: css`
|
|
||||||
display: flex;
|
|
||||||
`,
|
|
||||||
columnDetail: css`
|
|
||||||
.ant-description-textarea {
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
time {
|
|
||||||
display: block;
|
|
||||||
color: ${token.colorTextTertiary};
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
}));
|
|
||||||
|
|
||||||
function getResults({ approval, currentUser }) {
|
|
||||||
const { workflow, approvalExecutions, records } = approval;
|
|
||||||
|
|
||||||
approvalExecutions.sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
|
|
||||||
|
|
||||||
const approvalExecution = approvalExecutions.reduce(
|
|
||||||
(newObj, curr) =>
|
|
||||||
Object.assign(newObj, {
|
|
||||||
[curr.id]: Object.assign(curr, {
|
|
||||||
records: [
|
|
||||||
{
|
|
||||||
groupCount: 1,
|
|
||||||
node: {
|
|
||||||
title: lang('Apply'),
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
...approval.createdBy,
|
|
||||||
id: approval.createdById,
|
|
||||||
},
|
|
||||||
status: curr.status ? APPROVAL_STATUS.SUBMITTED : approval.status,
|
|
||||||
updatedAt: curr.createdAt,
|
|
||||||
execution: { ...curr },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
records
|
|
||||||
.sort((prevRecord, nextRecord) => {
|
|
||||||
const prev = new Date(prevRecord.job?.createdAt);
|
|
||||||
const next = new Date(nextRecord.job?.createdAt);
|
|
||||||
return prev < next ? -1 : prev > next ? 1 : prevRecord.id - nextRecord.id;
|
|
||||||
})
|
|
||||||
.forEach((record) => {
|
|
||||||
const approvalExecutionId = approvalExecution[record.approvalExecutionId];
|
|
||||||
const omitApprovalExecutionId = _.omit(approvalExecutionId, ['records']);
|
|
||||||
(record.workflow = workflow),
|
|
||||||
(record.execution = { ...omitApprovalExecutionId }),
|
|
||||||
approvalExecutionId.records.push(record),
|
|
||||||
approvalExecutionId.jobs || (approvalExecutionId.jobs = {}),
|
|
||||||
approvalExecutionId.jobs[record.jobId]
|
|
||||||
? (approvalExecutionId.jobs[record.jobId].first.groupCount += 1)
|
|
||||||
: ((approvalExecutionId.jobs[record.jobId] = { first: record }),
|
|
||||||
(record.groupCount = 1),
|
|
||||||
(record.statusCount = { [APPROVAL_ACTION_STATUS.APPROVED]: 0, [APPROVAL_ACTION_STATUS.REJECTED]: 0 })),
|
|
||||||
[APPROVAL_ACTION_STATUS.APPROVED, APPROVAL_ACTION_STATUS.REJECTED].includes(record.status) &&
|
|
||||||
(approvalExecutionId.jobs[record.jobId].first.statusCount[record.status] += 1);
|
|
||||||
}),
|
|
||||||
approval.createdById === (currentUser == null ? void 0 : currentUser.data.id) &&
|
|
||||||
approvalExecutions.forEach((approvalExecution) => {
|
|
||||||
approvalExecution.status === EXECUTION_STATUS.CANCELED &&
|
|
||||||
approvalExecution.records.length === 1 &&
|
|
||||||
((approvalExecution.records[0].groupCount = 2),
|
|
||||||
approvalExecution.records.push({
|
|
||||||
user: { nickname: approval.createdBy.nickname },
|
|
||||||
status: APPROVAL_ACTION_STATUS.WITHDRAWN,
|
|
||||||
updatedAt: approvalExecution.updatedAt,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
const aELength = approvalExecutions.length;
|
|
||||||
|
|
||||||
return approvalExecutions.filter(
|
|
||||||
(approvalExecution, index) =>
|
|
||||||
(aELength - 1 === index &&
|
|
||||||
(!approvalExecution.status || approvalExecution.status === EXECUTION_STATUS.CANCELED)) ||
|
|
||||||
approvalExecution.records.length > 1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { useCollectionRecordData } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { APPROVAL_ACTION_STATUS } from '../../../constants';
|
||||||
|
|
||||||
|
export const ApprovalLastNodeColumn = () => {
|
||||||
|
const approvalContext = useCollectionRecordData();
|
||||||
|
|
||||||
|
const lastNodeTitle = useMemo(() => getLastNodeTitle(approvalContext), [approvalContext]);
|
||||||
|
|
||||||
|
return <div>{lastNodeTitle}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getLastNodeTitle(approvalContext) {
|
||||||
|
const { records } = approvalContext;
|
||||||
|
let targetRecord = records.find(({ status }) => status !== APPROVAL_ACTION_STATUS.APPROVED);
|
||||||
|
if (!targetRecord) {
|
||||||
|
targetRecord = records.pop() || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastNodeTitle = targetRecord.node?.title;
|
||||||
|
|
||||||
|
return lastNodeTitle;
|
||||||
|
}
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { useCollectionManager, useCollectionRecordData, useCompile } from '@tachybase/client';
|
import { useCollectionManager, useCollectionRecordData, useCompile } from '@tachybase/client';
|
||||||
import { dayjs } from '@tachybase/utils/client';
|
import { dayjs } from '@tachybase/utils/client';
|
||||||
|
|
||||||
import useStyles from './style';
|
import useStyles from '../style';
|
||||||
|
|
||||||
export const ApprovalsSummary = (props) => {
|
export const ApprovalsSummary = (props) => {
|
||||||
const { value = '' } = props;
|
const { value = '' } = props;
|
@ -1,17 +1,17 @@
|
|||||||
import { Plugin } from '@tachybase/client';
|
import { Plugin } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { ApprovalLastNodeColumn } from './approval-columns/lastNode.column';
|
||||||
|
import { ApprovalsSummary } from './approval-columns/summary.column';
|
||||||
import { ApprovalDataProvider } from './ApprovalData.provider';
|
import { ApprovalDataProvider } from './ApprovalData.provider';
|
||||||
import { ApprovalProcess } from './ApprovalProcess.view';
|
import { ApprovalProcess } from './ApprovalProcess.view';
|
||||||
import { ApprovalsSummary } from './ApprovalsSummary.view';
|
|
||||||
|
|
||||||
export class KitApprovalCommon extends Plugin {
|
export class KitApprovalCommon extends Plugin {
|
||||||
async load() {
|
async load() {
|
||||||
this.app.addComponents({
|
this.app.addComponents({
|
||||||
// ApprovalCommon,
|
|
||||||
|
|
||||||
'ApprovalCommon.Provider.ApprovalDataProvider': ApprovalDataProvider,
|
'ApprovalCommon.Provider.ApprovalDataProvider': ApprovalDataProvider,
|
||||||
'ApprovalCommon.ViewComponent.ApprovalProcess': ApprovalProcess,
|
'ApprovalCommon.ViewComponent.ApprovalProcess': ApprovalProcess,
|
||||||
ApprovalsSummary: ApprovalsSummary,
|
ApprovalsSummary: ApprovalsSummary,
|
||||||
|
ApprovalLastNodeColumn: ApprovalLastNodeColumn,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ export const getAntdTableColumns = ({ t, styles }) => {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
render: renderColumnTaskNode,
|
render: renderColumnTaskNode,
|
||||||
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('User'),
|
title: t('User'),
|
@ -28,7 +28,6 @@ export function renderColumnTaskNode(text, { node, job, groupCount, statusCount
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
.ant-tag {
|
.ant-tag {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { EXECUTION_STATUS } from '../../../../constants';
|
||||||
|
import { APPROVAL_ACTION_STATUS, APPROVAL_STATUS } from '../../constants';
|
||||||
|
import { lang } from '../../locale';
|
||||||
|
|
||||||
|
export function getResults({ approval, currentUser }) {
|
||||||
|
const { workflow, approvalExecutions, records } = approval;
|
||||||
|
approvalExecutions.sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
|
||||||
|
|
||||||
|
const approvalExecution = approvalExecutions.reduce(
|
||||||
|
(newObj, curr) =>
|
||||||
|
Object.assign(newObj, {
|
||||||
|
[curr.id]: Object.assign(curr, {
|
||||||
|
records: [
|
||||||
|
{
|
||||||
|
groupCount: 1,
|
||||||
|
node: {
|
||||||
|
title: lang('Apply'),
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
...approval.createdBy,
|
||||||
|
id: approval.createdById,
|
||||||
|
},
|
||||||
|
status: curr.status ? APPROVAL_STATUS.SUBMITTED : approval.status,
|
||||||
|
updatedAt: curr.createdAt,
|
||||||
|
execution: { ...curr },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
records
|
||||||
|
.sort((prevRecord, nextRecord) => {
|
||||||
|
const prev = new Date(prevRecord.job?.createdAt);
|
||||||
|
const next = new Date(nextRecord.job?.createdAt);
|
||||||
|
return prev < next ? -1 : prev > next ? 1 : prevRecord.id - nextRecord.id;
|
||||||
|
})
|
||||||
|
.forEach((record) => {
|
||||||
|
const approvalExecutionId = approvalExecution[record.approvalExecutionId];
|
||||||
|
const omitApprovalExecutionId = _.omit(approvalExecutionId, ['records']);
|
||||||
|
(record.workflow = workflow),
|
||||||
|
(record.execution = { ...omitApprovalExecutionId }),
|
||||||
|
approvalExecutionId.records.push(record),
|
||||||
|
approvalExecutionId.jobs || (approvalExecutionId.jobs = {}),
|
||||||
|
approvalExecutionId.jobs[record.jobId]
|
||||||
|
? (approvalExecutionId.jobs[record.jobId].first.groupCount += 1)
|
||||||
|
: ((approvalExecutionId.jobs[record.jobId] = { first: record }),
|
||||||
|
(record.groupCount = 1),
|
||||||
|
(record.statusCount = { [APPROVAL_ACTION_STATUS.APPROVED]: 0, [APPROVAL_ACTION_STATUS.REJECTED]: 0 })),
|
||||||
|
[APPROVAL_ACTION_STATUS.APPROVED, APPROVAL_ACTION_STATUS.REJECTED].includes(record.status) &&
|
||||||
|
(approvalExecutionId.jobs[record.jobId].first.statusCount[record.status] += 1);
|
||||||
|
}),
|
||||||
|
approval.createdById === (currentUser == null ? void 0 : currentUser.data.id) &&
|
||||||
|
approvalExecutions.forEach((approvalExecution) => {
|
||||||
|
approvalExecution.status === EXECUTION_STATUS.CANCELED &&
|
||||||
|
approvalExecution.records.length === 1 &&
|
||||||
|
((approvalExecution.records[0].groupCount = 2),
|
||||||
|
approvalExecution.records.push({
|
||||||
|
user: { nickname: approval.createdBy.nickname },
|
||||||
|
status: APPROVAL_ACTION_STATUS.WITHDRAWN,
|
||||||
|
updatedAt: approvalExecution.updatedAt,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
const aELength = approvalExecutions.length;
|
||||||
|
return approvalExecutions.filter(
|
||||||
|
(approvalExecution, index) =>
|
||||||
|
(aELength - 1 === index &&
|
||||||
|
(!approvalExecution.status || approvalExecution.status === EXECUTION_STATUS.CANCELED)) ||
|
||||||
|
approvalExecution.records.length > 1,
|
||||||
|
);
|
||||||
|
}
|
@ -3,6 +3,7 @@ import { UiSchemaRepository } from '@tachybase/plugin-ui-schema-storage';
|
|||||||
import { JOB_STATUS } from '../../constants';
|
import { JOB_STATUS } from '../../constants';
|
||||||
import Instruction from '../../instructions';
|
import Instruction from '../../instructions';
|
||||||
import { COLLECTION_NAME_APPROVAL_CARBON_COPY } from '../common/constants';
|
import { COLLECTION_NAME_APPROVAL_CARBON_COPY } from '../common/constants';
|
||||||
|
import { APPROVAL_STATUS } from './constants';
|
||||||
import { parsePerson } from './tools';
|
import { parsePerson } from './tools';
|
||||||
|
|
||||||
export default class ApprovalCarbonCopyInstruction extends Instruction {
|
export default class ApprovalCarbonCopyInstruction extends Instruction {
|
||||||
@ -25,27 +26,29 @@ export default class ApprovalCarbonCopyInstruction extends Instruction {
|
|||||||
appends: ['approvalExecutions', 'createdBy'],
|
appends: ['approvalExecutions', 'createdBy'],
|
||||||
except: ['data'],
|
except: ['data'],
|
||||||
});
|
});
|
||||||
const CarbonCopyModel = db.getModel(COLLECTION_NAME_APPROVAL_CARBON_COPY);
|
// NOTE: 只有新发起审批的时候, 才生成抄送副本. 否则, 会生成不必要的重复副本
|
||||||
const notifiedPersonDataMap = targetPersonList.map((userId, index) => ({
|
if ([APPROVAL_STATUS.SUBMITTED].includes(approval.status)) {
|
||||||
userId,
|
const CarbonCopyModel = db.getModel(COLLECTION_NAME_APPROVAL_CARBON_COPY);
|
||||||
jobId: job.id,
|
const notifiedPersonDataMap = targetPersonList.map((userId, index) => ({
|
||||||
nodeId: node.id,
|
userId,
|
||||||
executionId: job.executionId,
|
jobId: job.id,
|
||||||
workflowId: node.workflowId,
|
nodeId: node.id,
|
||||||
index,
|
executionId: job.executionId,
|
||||||
// TODO: 怎么做到状态实时更新, 观察下审批发起的表的状态是怎么做的...
|
workflowId: node.workflowId,
|
||||||
createdById: approval.createdBy?.id,
|
index,
|
||||||
approvalId: approval.id,
|
createdById: approval.createdBy?.id,
|
||||||
status: approval.status,
|
approvalId: approval.id,
|
||||||
snapshot: approval.data,
|
status: approval.status,
|
||||||
summary: approval.summary,
|
snapshot: approval.data,
|
||||||
collectionName: approval.collectionName,
|
summary: approval.summary,
|
||||||
dataKey: approval.dataKey,
|
collectionName: approval.collectionName,
|
||||||
}));
|
dataKey: approval.dataKey,
|
||||||
|
}));
|
||||||
|
|
||||||
await CarbonCopyModel.bulkCreate(notifiedPersonDataMap, {
|
await CarbonCopyModel.bulkCreate(notifiedPersonDataMap, {
|
||||||
transaction: processor.transaction,
|
transaction: processor.transaction,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return job;
|
return job;
|
||||||
|
@ -370,6 +370,7 @@ const approvalCarbonCopy = {
|
|||||||
context.action.mergeParams({
|
context.action.mergeParams({
|
||||||
filter: {
|
filter: {
|
||||||
workflowId: centralizedApprovalFlow.map((item) => item.id),
|
workflowId: centralizedApprovalFlow.map((item) => item.id),
|
||||||
|
// approval.status: APPROVAL_STATUS.DRAFT,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return actions.list(context, next);
|
return actions.list(context, next);
|
||||||
|
Loading…
Reference in New Issue
Block a user