@nocobase/plugin-workflow-manual 2.1.30 → 2.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  module.exports = {
11
- "@nocobase/client": "2.1.30",
11
+ "@nocobase/client": "2.1.32",
12
12
  "react": "18.2.0",
13
13
  "react-router-dom": "6.30.1",
14
14
  "@formily/react": "2.3.7",
@@ -19,14 +19,14 @@ module.exports = {
19
19
  "dayjs": "1.11.13",
20
20
  "react-i18next": "11.18.6",
21
21
  "lodash": "4.18.1",
22
- "@nocobase/plugin-workflow": "2.1.30",
23
- "@nocobase/plugin-mobile": "2.1.30",
24
- "@nocobase/utils": "2.1.30",
25
- "@nocobase/server": "2.1.30",
26
- "@nocobase/database": "2.1.30",
27
- "@nocobase/actions": "2.1.30",
28
- "@nocobase/test": "2.1.30",
29
- "@nocobase/plugin-workflow-test": "2.1.30",
22
+ "@nocobase/plugin-workflow": "2.1.32",
23
+ "@nocobase/plugin-mobile": "2.1.32",
24
+ "@nocobase/utils": "2.1.32",
25
+ "@nocobase/server": "2.1.32",
26
+ "@nocobase/database": "2.1.32",
27
+ "@nocobase/actions": "2.1.32",
28
+ "@nocobase/test": "2.1.32",
29
+ "@nocobase/plugin-workflow-test": "2.1.32",
30
30
  "@formily/core": "2.3.7",
31
31
  "sequelize": "6.35.2"
32
32
  };
@@ -1 +1 @@
1
- {"name":"joi","description":"Object schema validation","version":"17.13.3","repository":"git://github.com/hapijs/joi","main":"lib/index.js","types":"lib/index.d.ts","browser":"dist/joi-browser.min.js","files":["lib/**/*","dist/*"],"keywords":["schema","validation"],"dependencies":{"@hapi/hoek":"^9.3.0","@hapi/topo":"^5.1.0","@sideway/address":"^4.1.5","@sideway/formula":"^3.0.1","@sideway/pinpoint":"^2.0.0"},"devDependencies":{"@hapi/bourne":"2.x.x","@hapi/code":"8.x.x","@hapi/joi-legacy-test":"npm:@hapi/joi@15.x.x","@hapi/lab":"^25.1.3","@types/node":"^14.18.63","typescript":"4.3.x"},"scripts":{"prepublishOnly":"cd browser && npm install && npm run build","test":"lab -t 100 -a @hapi/code -L -Y","test-cov-html":"lab -r html -o coverage.html -a @hapi/code"},"license":"BSD-3-Clause","_lastModified":"2026-07-24T00:54:03.848Z"}
1
+ {"name":"joi","description":"Object schema validation","version":"17.13.3","repository":"git://github.com/hapijs/joi","main":"lib/index.js","types":"lib/index.d.ts","browser":"dist/joi-browser.min.js","files":["lib/**/*","dist/*"],"keywords":["schema","validation"],"dependencies":{"@hapi/hoek":"^9.3.0","@hapi/topo":"^5.1.0","@sideway/address":"^4.1.5","@sideway/formula":"^3.0.1","@sideway/pinpoint":"^2.0.0"},"devDependencies":{"@hapi/bourne":"2.x.x","@hapi/code":"8.x.x","@hapi/joi-legacy-test":"npm:@hapi/joi@15.x.x","@hapi/lab":"^25.1.3","@types/node":"^14.18.63","typescript":"4.3.x"},"scripts":{"prepublishOnly":"cd browser && npm install && npm run build","test":"lab -t 100 -a @hapi/code -L -Y","test-cov-html":"lab -r html -o coverage.html -a @hapi/code"},"license":"BSD-3-Clause","_lastModified":"2026-07-29T05:59:41.638Z"}
@@ -7,7 +7,8 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { Registry } from '@nocobase/utils';
10
- import WorkflowPlugin, { Processor, Instruction } from '@nocobase/plugin-workflow';
10
+ import WorkflowPlugin, { Instruction, Processor } from '@nocobase/plugin-workflow';
11
+ import type { FlowNodeModel, JobModel } from '@nocobase/plugin-workflow';
11
12
  import { FormHandler } from './forms';
12
13
  type FormType = {
13
14
  type: 'custom' | 'create' | 'update';
@@ -31,7 +32,7 @@ export default class extends Instruction {
31
32
  workflow: WorkflowPlugin;
32
33
  formTypes: Registry<FormHandler>;
33
34
  constructor(workflow: WorkflowPlugin);
34
- run(node: any, prevJob: any, processor: Processor): Promise<import("@nocobase/plugin-workflow").JobModel>;
35
- resume(node: any, job: any, processor: Processor): Promise<any>;
35
+ run(node: any, prevJob: any, processor: Processor): Promise<JobModel>;
36
+ resume(node: FlowNodeModel, job: JobModel, processor: Processor): Promise<JobModel>;
36
37
  }
37
38
  export {};
@@ -42,64 +42,6 @@ module.exports = __toCommonJS(ManualInstruction_exports);
42
42
  var import_utils = require("@nocobase/utils");
43
43
  var import_plugin_workflow = require("@nocobase/plugin-workflow");
44
44
  var import_forms = __toESM(require("./forms"));
45
- const MULTIPLE_ASSIGNED_MODE = {
46
- SINGLE: Symbol("single"),
47
- ALL: Symbol("all"),
48
- ANY: Symbol("any"),
49
- ALL_PERCENTAGE: Symbol("all percentage"),
50
- ANY_PERCENTAGE: Symbol("any percentage")
51
- };
52
- const Modes = {
53
- [MULTIPLE_ASSIGNED_MODE.SINGLE]: {
54
- getStatus(distribution, assignees) {
55
- const done = distribution.find((item) => item.status !== import_plugin_workflow.JOB_STATUS.PENDING && item.count > 0);
56
- return done ? done.status : null;
57
- }
58
- },
59
- [MULTIPLE_ASSIGNED_MODE.ALL]: {
60
- getStatus(distribution, assignees) {
61
- const resolved = distribution.find((item) => item.status === import_plugin_workflow.JOB_STATUS.RESOLVED);
62
- if (resolved && resolved.count === assignees.length) {
63
- return import_plugin_workflow.JOB_STATUS.RESOLVED;
64
- }
65
- const rejected = distribution.find((item) => item.status < import_plugin_workflow.JOB_STATUS.PENDING);
66
- if (rejected && rejected.count) {
67
- return rejected.status;
68
- }
69
- return null;
70
- }
71
- },
72
- [MULTIPLE_ASSIGNED_MODE.ANY]: {
73
- getStatus(distribution, assignees) {
74
- const resolved = distribution.find((item) => item.status === import_plugin_workflow.JOB_STATUS.RESOLVED);
75
- if (resolved && resolved.count) {
76
- return import_plugin_workflow.JOB_STATUS.RESOLVED;
77
- }
78
- const rejectedCount = distribution.reduce(
79
- (count, item) => item.status < import_plugin_workflow.JOB_STATUS.PENDING ? count + item.count : count,
80
- 0
81
- );
82
- if (rejectedCount === assignees.length) {
83
- return import_plugin_workflow.JOB_STATUS.REJECTED;
84
- }
85
- return null;
86
- }
87
- }
88
- };
89
- function getMode(mode) {
90
- switch (true) {
91
- case mode === 1:
92
- return Modes[MULTIPLE_ASSIGNED_MODE.ALL];
93
- case mode === -1:
94
- return Modes[MULTIPLE_ASSIGNED_MODE.ANY];
95
- case mode > 0:
96
- return Modes[MULTIPLE_ASSIGNED_MODE.ALL_PERCENTAGE];
97
- case mode < 0:
98
- return Modes[MULTIPLE_ASSIGNED_MODE.ANY_PERCENTAGE];
99
- default:
100
- return Modes[MULTIPLE_ASSIGNED_MODE.SINGLE];
101
- }
102
- }
103
45
  class ManualInstruction_default extends import_plugin_workflow.Instruction {
104
46
  constructor(workflow) {
105
47
  super(workflow);
@@ -136,35 +78,7 @@ class ManualInstruction_default extends import_plugin_workflow.Instruction {
136
78
  return job;
137
79
  }
138
80
  async resume(node, job, processor) {
139
- var _a;
140
- const { mode } = node.config;
141
- const TaskRepo = this.workflow.app.db.getRepository("workflowManualTasks");
142
- const tasks = await TaskRepo.find({
143
- where: {
144
- jobId: job.id
145
- }
146
- });
147
- const assignees = [];
148
- const distributionMap = tasks.reduce((result2, item) => {
149
- if (result2[item.status] == null) {
150
- result2[item.status] = 0;
151
- }
152
- result2[item.status] += 1;
153
- assignees.push(item.userId);
154
- return result2;
155
- }, {});
156
- const distribution = Object.keys(distributionMap).map((status2) => ({
157
- status: Number.parseInt(status2, 10),
158
- count: distributionMap[status2]
159
- }));
160
- const submitted = tasks.reduce((count, item) => item.status !== import_plugin_workflow.JOB_STATUS.PENDING ? count + 1 : count, 0);
161
- const status = job.status || (getMode(mode).getStatus(distribution, assignees) ?? import_plugin_workflow.JOB_STATUS.PENDING);
162
- const result = mode ? (submitted || 0) / assignees.length : ((_a = job.latestTask) == null ? void 0 : _a.result) ?? job.result;
163
- processor.logger.debug(`manual resume job and next status: ${status}`);
164
- job.set({
165
- status,
166
- result
167
- });
81
+ processor.logger.debug(`manual resume job and next status: ${job.status}`);
168
82
  return job;
169
83
  }
170
84
  }
@@ -41,7 +41,62 @@ __export(actions_exports, {
41
41
  });
42
42
  module.exports = __toCommonJS(actions_exports);
43
43
  var import_actions = __toESM(require("@nocobase/actions"));
44
+ var import_database = require("@nocobase/database");
44
45
  var import_plugin_workflow = __toESM(require("@nocobase/plugin-workflow"));
46
+ function getSubmittedRatio(tasks) {
47
+ if (!tasks.length) {
48
+ return 0;
49
+ }
50
+ const submitted = tasks.reduce((count, item) => item.status !== import_plugin_workflow.JOB_STATUS.PENDING ? count + 1 : count, 0);
51
+ return submitted / tasks.length;
52
+ }
53
+ function getSingleModeStatus(distribution) {
54
+ var _a;
55
+ return ((_a = distribution.find((item) => item.status !== import_plugin_workflow.JOB_STATUS.PENDING && item.count > 0)) == null ? void 0 : _a.status) ?? null;
56
+ }
57
+ function getAllModeStatus(distribution, assignees) {
58
+ const resolved = distribution.find((item) => item.status === import_plugin_workflow.JOB_STATUS.RESOLVED);
59
+ if ((resolved == null ? void 0 : resolved.count) === assignees.length) {
60
+ return import_plugin_workflow.JOB_STATUS.RESOLVED;
61
+ }
62
+ const rejected = distribution.find((item) => item.status < import_plugin_workflow.JOB_STATUS.PENDING);
63
+ return (rejected == null ? void 0 : rejected.count) ? rejected.status : null;
64
+ }
65
+ function getAnyModeStatus(distribution, assignees) {
66
+ const resolved = distribution.find((item) => item.status === import_plugin_workflow.JOB_STATUS.RESOLVED);
67
+ if (resolved == null ? void 0 : resolved.count) {
68
+ return import_plugin_workflow.JOB_STATUS.RESOLVED;
69
+ }
70
+ const rejectedCount = distribution.reduce(
71
+ (count, item) => item.status < import_plugin_workflow.JOB_STATUS.PENDING ? count + item.count : count,
72
+ 0
73
+ );
74
+ return rejectedCount === assignees.length ? import_plugin_workflow.JOB_STATUS.REJECTED : null;
75
+ }
76
+ async function updateJobByManualTasks(job, node, database, latestTask, transaction) {
77
+ const mode = node.config.mode ?? 0;
78
+ const tasks = await database.getModel("workflowManualTasks").findAll({
79
+ where: {
80
+ jobId: job.id
81
+ },
82
+ transaction
83
+ });
84
+ const assignees = [];
85
+ const distributionMap = /* @__PURE__ */ new Map();
86
+ for (const task of tasks) {
87
+ distributionMap.set(task.status, (distributionMap.get(task.status) ?? 0) + 1);
88
+ assignees.push(task.userId);
89
+ }
90
+ const distribution = Array.from(distributionMap.entries()).map(([status2, count]) => ({ status: status2, count }));
91
+ const status = mode === 1 ? getAllModeStatus(distribution, assignees) : mode === -1 ? getAnyModeStatus(distribution, assignees) : getSingleModeStatus(distribution);
92
+ await job.update(
93
+ {
94
+ status: status ?? import_plugin_workflow.JOB_STATUS.PENDING,
95
+ result: mode ? getSubmittedRatio(tasks) : latestTask.result ?? job.result
96
+ },
97
+ { transaction }
98
+ );
99
+ }
45
100
  async function submit(context, next) {
46
101
  var _a, _b, _c;
47
102
  const repository = import_actions.utils.getRepositoryFromParams(context);
@@ -78,48 +133,99 @@ async function submit(context, next) {
78
133
  return context.throw(400);
79
134
  }
80
135
  task.execution.workflow = task.workflow;
81
- const processor = plugin.createProcessor(task.execution);
82
- await processor.prepare();
83
- const assignees = processor.getParsedValue(task.node.config.assignees ?? [], task.nodeId).flat().filter(Boolean);
84
- if (!assignees.includes(currentUser.id) || task.userId !== currentUser.id) {
85
- return context.throw(403);
86
- }
87
- const presetValues = processor.getParsedValue(actionItem.values ?? {}, task.nodeId, {
88
- additionalScope: {
89
- // @deprecated
90
- currentUser,
91
- // @deprecated
92
- currentRecord: values.result[formKey],
93
- // @deprecated
94
- currentTime: /* @__PURE__ */ new Date(),
95
- $user: currentUser,
96
- $nForm: values.result[formKey],
97
- $nDate: {
98
- now: /* @__PURE__ */ new Date()
136
+ const formConfig = forms[formKey];
137
+ const handler = instruction.formTypes.get(formConfig.type);
138
+ const usesMainDataSource = (formConfig.dataSource ?? "main") === "main";
139
+ const transactionOptions = context.db.sequelize.getDialect() === "sqlite" ? { type: import_database.Transaction.TYPES.IMMEDIATE } : {};
140
+ const validateTask = () => {
141
+ if (task.status !== import_plugin_workflow.JOB_STATUS.PENDING || task.job.status !== import_plugin_workflow.JOB_STATUS.PENDING || task.execution.status !== import_plugin_workflow.EXECUTION_STATUS.STARTED) {
142
+ return context.throw(400);
143
+ }
144
+ };
145
+ const getPresetValues = (processor) => {
146
+ const assignees = processor.getParsedValue(task.node.config.assignees ?? [], task.nodeId).flat().filter(Boolean);
147
+ if (!assignees.includes(currentUser.id) || task.userId !== currentUser.id) {
148
+ return context.throw(403);
149
+ }
150
+ return processor.getParsedValue(actionItem.values ?? {}, task.nodeId, {
151
+ additionalScope: {
152
+ // @deprecated
153
+ currentUser,
154
+ // @deprecated
155
+ currentRecord: values.result[formKey],
156
+ // @deprecated
157
+ currentTime: /* @__PURE__ */ new Date(),
158
+ $user: currentUser,
159
+ $nForm: values.result[formKey],
160
+ $nDate: {
161
+ now: /* @__PURE__ */ new Date()
162
+ }
99
163
  }
164
+ });
165
+ };
166
+ const setTaskResult = (presetValues) => {
167
+ task.set({
168
+ status: actionItem.status,
169
+ result: actionItem.status ? { [formKey]: { ...values.result[formKey], ...presetValues }, _: actionKey } : { ...task.result ?? {}, ...values.result }
170
+ });
171
+ task.changed("result", true);
172
+ };
173
+ const handleFormSubmission = async (transaction) => {
174
+ const processor = plugin.createProcessor(task.execution, { transaction });
175
+ await processor.prepare();
176
+ const presetValues = getPresetValues(processor);
177
+ setTaskResult(presetValues);
178
+ if (handler && task.status) {
179
+ const { actions: actions2, ...formOptions } = formConfig;
180
+ const parsedFormConfig = {
181
+ ...processor.getParsedValue(formOptions, task.nodeId),
182
+ actions: actions2
183
+ };
184
+ await handler.call(instruction, task, parsedFormConfig, transaction);
100
185
  }
101
- });
102
- task.set({
103
- status: actionItem.status,
104
- result: actionItem.status ? { [formKey]: { ...values.result[formKey], ...presetValues }, _: actionKey } : { ...task.result ?? {}, ...values.result }
105
- });
106
- task.changed("result", true);
107
- const handler = instruction.formTypes.get(forms[formKey].type);
108
- if (handler && task.status) {
109
- await handler.call(instruction, task, forms[formKey], processor);
186
+ return presetValues;
187
+ };
188
+ let shouldResume = false;
189
+ try {
190
+ const lock = await context.app.lockManager.tryAcquire((0, import_plugin_workflow.getJobLockKey)(task.job.id));
191
+ await lock.runExclusive(async () => {
192
+ let presetValues = {};
193
+ if (!usesMainDataSource) {
194
+ await Promise.all([task.reload(), task.job.reload(), task.execution.reload()]);
195
+ validateTask();
196
+ presetValues = await handleFormSubmission();
197
+ }
198
+ await context.db.sequelize.transaction(transactionOptions, async (transaction) => {
199
+ await Promise.all([
200
+ task.reload({ transaction }),
201
+ task.job.reload({ transaction }),
202
+ task.execution.reload({ transaction })
203
+ ]);
204
+ validateTask();
205
+ if (usesMainDataSource) {
206
+ await handleFormSubmission(transaction);
207
+ } else {
208
+ setTaskResult(presetValues);
209
+ }
210
+ await task.save({ transaction });
211
+ await updateJobByManualTasks(task.job, task.node, context.db, task, transaction);
212
+ shouldResume = task.job.status !== import_plugin_workflow.JOB_STATUS.PENDING;
213
+ });
214
+ }, 6e4);
215
+ } catch (error) {
216
+ if ((0, import_plugin_workflow.isLockAcquireError)(error)) {
217
+ return context.throw(409);
218
+ }
219
+ throw error;
110
220
  }
111
- await task.save();
112
- await processor.exit();
113
221
  context.body = task;
114
222
  context.status = 202;
115
223
  await next();
116
- if (task.execution.status !== import_plugin_workflow.EXECUTION_STATUS.STARTED) {
224
+ if (!shouldResume) {
117
225
  return;
118
226
  }
119
- task.job.execution = task.execution;
120
- task.job.latestTask = task;
121
- processor.logger.info(`manual node (${task.nodeId}) action trigger execution (${task.execution.id}) to resume`);
122
- plugin.resume(task.job);
227
+ plugin.getLogger(task.execution.workflowId).info(`manual node (${task.nodeId}) action trigger execution (${task.execution.id}) to resume`);
228
+ plugin.resume(task.job).catch(() => void 0);
123
229
  }
124
230
  async function listMine(context, next) {
125
231
  context.action.mergeParams({
@@ -6,9 +6,9 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
- import { Processor } from '@nocobase/plugin-workflow';
9
+ import type { Transaction } from '@nocobase/database';
10
10
  import ManualInstruction from '../ManualInstruction';
11
11
  export default function (this: ManualInstruction, instance: any, { dataSource, collection }: {
12
12
  dataSource?: string;
13
13
  collection: any;
14
- }, processor: Processor): Promise<void>;
14
+ }, transaction?: Transaction): Promise<void>;
@@ -29,7 +29,7 @@ __export(create_exports, {
29
29
  default: () => create_default
30
30
  });
31
31
  module.exports = __toCommonJS(create_exports);
32
- async function create_default(instance, { dataSource = "main", collection }, processor) {
32
+ async function create_default(instance, { dataSource = "main", collection }, transaction) {
33
33
  const repo = this.workflow.app.dataSourceManager.dataSources.get(dataSource).collectionManager.getRepository(collection);
34
34
  if (!repo) {
35
35
  throw new Error(`collection ${collection} for create data on manual node not found`);
@@ -43,8 +43,8 @@ async function create_default(instance, { dataSource = "main", collection }, pro
43
43
  updatedBy: instance.userId
44
44
  },
45
45
  context: {
46
- executionId: processor.execution.id
46
+ executionId: instance.executionId
47
47
  },
48
- transaction: processor.transaction
48
+ transaction
49
49
  });
50
50
  }
@@ -6,9 +6,9 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
- import { Processor } from '@nocobase/plugin-workflow';
9
+ import type { Transaction } from '@nocobase/database';
10
10
  import ManualInstruction from '../ManualInstruction';
11
- export type FormHandler = (this: ManualInstruction, instance: any, formConfig: any, processor: Processor) => Promise<void>;
11
+ export type FormHandler = (this: ManualInstruction, instance: any, formConfig: any, transaction?: Transaction) => Promise<void>;
12
12
  export default function ({ formTypes }: {
13
13
  formTypes: any;
14
14
  }): void;
@@ -6,10 +6,10 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
- import { Processor } from '@nocobase/plugin-workflow';
9
+ import type { Transaction } from '@nocobase/database';
10
10
  import ManualInstruction from '../ManualInstruction';
11
11
  export default function (this: ManualInstruction, instance: any, { dataSource, collection, filter }: {
12
12
  dataSource?: string;
13
13
  collection: any;
14
14
  filter?: {};
15
- }, processor: Processor): Promise<void>;
15
+ }, transaction?: Transaction): Promise<void>;
@@ -29,7 +29,7 @@ __export(update_exports, {
29
29
  default: () => update_default
30
30
  });
31
31
  module.exports = __toCommonJS(update_exports);
32
- async function update_default(instance, { dataSource = "main", collection, filter = {} }, processor) {
32
+ async function update_default(instance, { dataSource = "main", collection, filter = {} }, transaction) {
33
33
  const repo = this.workflow.app.dataSourceManager.dataSources.get(dataSource).collectionManager.getRepository(collection);
34
34
  if (!repo) {
35
35
  throw new Error(`collection ${collection} for update data on manual node not found`);
@@ -37,14 +37,14 @@ async function update_default(instance, { dataSource = "main", collection, filte
37
37
  const { _, ...form } = instance.result;
38
38
  const [values] = Object.values(form);
39
39
  await repo.update({
40
- filter: processor.getParsedValue(filter, instance.nodeId),
40
+ filter,
41
41
  values: {
42
42
  ...values ?? {},
43
43
  updatedBy: instance.userId
44
44
  },
45
45
  context: {
46
- executionId: processor.execution.id
46
+ executionId: instance.executionId
47
47
  },
48
- transaction: processor.transaction
48
+ transaction
49
49
  });
50
50
  }
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "Could be used for workflows which some of decisions are made by users.",
7
7
  "description.ru-RU": "Может использоваться в рабочих процессах, где какие-то решения принимаются пользователями.",
8
8
  "description.zh-CN": "用于人工控制部分决策的流程。",
9
- "version": "2.1.30",
9
+ "version": "2.1.32",
10
10
  "license": "Apache-2.0",
11
11
  "main": "./dist/server/index.js",
12
12
  "homepage": "https://docs.nocobase.com/handbook/workflow-manual",
@@ -35,7 +35,7 @@
35
35
  "@nocobase/test": "2.x",
36
36
  "@nocobase/utils": "2.x"
37
37
  },
38
- "gitHead": "c1a4bff0fd3c8b30e06fc6f50ad33e054b04061a",
38
+ "gitHead": "737a1324f650d7bc66594203b68eb0180553a551",
39
39
  "keywords": [
40
40
  "Workflow"
41
41
  ]