@mldong/jeeflow 1.5.0 → 1.6.0

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.
@@ -0,0 +1,59 @@
1
+ import type { FlowNode, ProcessInstance } from './model.js';
2
+ import type { IAssignmentHandler } from './registry.js';
3
+ import type { OrgUserProvider, UserProvider } from './spi.js';
4
+ export declare const HANDLER_OPERATOR_ASSIGNMENT = "com.mldong.jeeflow.interceptor.impl.OperatorAssignmentHandler";
5
+ export declare const HANDLER_FORM_FIELD_ASSIGNEE = "com.mldong.jeeflow.interceptor.impl.FormFieldAssigneeHandler";
6
+ export declare const HANDLER_DEPT_LEADER: string;
7
+ export declare const HANDLER_DEPT_MAIN_LEADER: string;
8
+ export declare const HANDLER_APPLICANT_DEPT_LEADER: string;
9
+ export declare const HANDLER_APPLICANT_DEPT_MAIN_LEADER: string;
10
+ export declare const HANDLER_TASK_ROLE_ASSIGNEE: string;
11
+ /** 流程发起人(兜底 "apply.operator") */
12
+ export declare class OperatorAssignmentHandler implements IAssignmentHandler {
13
+ assign(_node: FlowNode, inst: ProcessInstance | null, _operator: string): Promise<string[]>;
14
+ }
15
+ /** 按表单字段值分配参与者:精确匹配 node.id → vars 字段;_数字 后缀去后缀再匹配。 */
16
+ export declare class FormFieldAssigneeHandler implements IAssignmentHandler {
17
+ assign(node: FlowNode, inst: ProcessInstance | null, _operator: string): Promise<string[]>;
18
+ private findFieldValue;
19
+ private collect;
20
+ private add;
21
+ }
22
+ /** 组织维度 handler 公共依赖 */
23
+ declare class OrgBase {
24
+ protected userProv?: UserProvider | undefined;
25
+ protected orgProv?: OrgUserProvider | undefined;
26
+ constructor(userProv?: UserProvider | undefined, orgProv?: OrgUserProvider | undefined);
27
+ protected byDept(deptId: string, main: boolean): Promise<string[]>;
28
+ protected deptIdOf(userId: string): Promise<string>;
29
+ }
30
+ /** 当前用户(任务操作人)部门领导 */
31
+ export declare class DeptLeaderAssignmentHandler extends OrgBase implements IAssignmentHandler {
32
+ assign(_node: FlowNode, _inst: ProcessInstance | null, operator: string): Promise<string[]>;
33
+ }
34
+ /** 当前用户(任务操作人)部门分管领导 */
35
+ export declare class DeptMainLeaderAssignmentHandler extends OrgBase implements IAssignmentHandler {
36
+ assign(_node: FlowNode, _inst: ProcessInstance | null, operator: string): Promise<string[]>;
37
+ }
38
+ /** 发起人部门领导 */
39
+ export declare class ApplicantDeptLeaderAssignmentHandler extends OrgBase implements IAssignmentHandler {
40
+ assign(_node: FlowNode, inst: ProcessInstance | null, _operator: string): Promise<string[]>;
41
+ }
42
+ /** 发起人部门分管领导 */
43
+ export declare class ApplicantDeptMainLeaderAssignmentHandler extends OrgBase implements IAssignmentHandler {
44
+ assign(_node: FlowNode, inst: ProcessInstance | null, _operator: string): Promise<string[]>;
45
+ }
46
+ /** 任务节点唯一编码关联角色(roleCode = 节点 id) */
47
+ export declare class TaskRoleAssigneeHandler implements IAssignmentHandler {
48
+ private orgProv?;
49
+ constructor(orgProv?: OrgUserProvider | undefined);
50
+ assign(node: FlowNode, _inst: ProcessInstance | null, _operator: string): Promise<string[]>;
51
+ }
52
+ /**
53
+ * 注册内置通用参与者处理器到注册表(组织维度 handler 依赖 userProv/orgProv)。
54
+ * 与 HandlerRegistry.registerAssignment 组合使用。
55
+ */
56
+ export declare function registerBuiltinAssignments(reg: {
57
+ registerAssignment(name: string, handler: IAssignmentHandler): void;
58
+ }, userProv?: UserProvider, orgProv?: OrgUserProvider): void;
59
+ export {};
@@ -0,0 +1,135 @@
1
+ // ─── 内置通用参与者处理器(issues/16)───────────────────────────────────────────
2
+ // 注册名与 Java 类全限定名一致,跨语言流程 JSON 通用(前端设计器配置天然兼容)。
3
+ // OperatorAssignmentHandler / FormFieldAssigneeHandler 为纯引擎语义,零外部依赖;
4
+ // 组织维度 handler 通过 OrgUserProvider SPI 取数据,业务方只实现数据接口。
5
+ export const HANDLER_OPERATOR_ASSIGNMENT = 'com.mldong.jeeflow.interceptor.impl.OperatorAssignmentHandler';
6
+ export const HANDLER_FORM_FIELD_ASSIGNEE = 'com.mldong.jeeflow.interceptor.impl.FormFieldAssigneeHandler';
7
+ const ORG_HANDLERS_PREFIX = 'com.mldong.jeeflow.interceptor.impl.OrgUserAssignmentHandlers$';
8
+ export const HANDLER_DEPT_LEADER = ORG_HANDLERS_PREFIX + 'DeptLeaderAssignmentHandler';
9
+ export const HANDLER_DEPT_MAIN_LEADER = ORG_HANDLERS_PREFIX + 'DeptMainLeaderAssignmentHandler';
10
+ export const HANDLER_APPLICANT_DEPT_LEADER = ORG_HANDLERS_PREFIX + 'ApplicantDeptLeaderAssignmentHandler';
11
+ export const HANDLER_APPLICANT_DEPT_MAIN_LEADER = ORG_HANDLERS_PREFIX + 'ApplicantDeptMainLeaderAssignmentHandler';
12
+ export const HANDLER_TASK_ROLE_ASSIGNEE = ORG_HANDLERS_PREFIX + 'TaskRoleAssigneeHandler';
13
+ // 表单字段编号后缀正则(task_01 → task)
14
+ const NUMBER_SUFFIX_PATTERN = /^(.+?)_(\d+)$/;
15
+ // ─── 纯引擎语义 ────────────────────────────────────────────────────────────────
16
+ /** 流程发起人(兜底 "apply.operator") */
17
+ export class OperatorAssignmentHandler {
18
+ async assign(_node, inst, _operator) {
19
+ if (inst?.operator)
20
+ return [inst.operator];
21
+ return ['apply.operator'];
22
+ }
23
+ }
24
+ /** 按表单字段值分配参与者:精确匹配 node.id → vars 字段;_数字 后缀去后缀再匹配。 */
25
+ export class FormFieldAssigneeHandler {
26
+ async assign(node, inst, _operator) {
27
+ if (!inst || !node)
28
+ return [];
29
+ const value = this.findFieldValue(inst.variables, node.id);
30
+ if (value == null)
31
+ return [];
32
+ return this.collect(value);
33
+ }
34
+ findFieldValue(variables, fieldName) {
35
+ if (fieldName in variables)
36
+ return variables[fieldName];
37
+ const m = NUMBER_SUFFIX_PATTERN.exec(fieldName);
38
+ if (m && m[1] in variables)
39
+ return variables[m[1]];
40
+ return null;
41
+ }
42
+ collect(value) {
43
+ const ids = [];
44
+ if (Array.isArray(value)) {
45
+ for (const item of value)
46
+ this.add(ids, String(item));
47
+ }
48
+ else {
49
+ this.add(ids, String(value));
50
+ }
51
+ return ids;
52
+ }
53
+ add(ids, token) {
54
+ for (const s of token.split(',')) {
55
+ const t = s.trim();
56
+ if (t && !ids.includes(t))
57
+ ids.push(t);
58
+ }
59
+ }
60
+ }
61
+ // ─── 组织维度(OrgUserProvider SPI)───────────────────────────────────────────
62
+ /** 组织维度 handler 公共依赖 */
63
+ class OrgBase {
64
+ userProv;
65
+ orgProv;
66
+ constructor(userProv, orgProv) {
67
+ this.userProv = userProv;
68
+ this.orgProv = orgProv;
69
+ }
70
+ async byDept(deptId, main) {
71
+ if (!deptId || !this.orgProv)
72
+ return [];
73
+ return main ? await this.orgProv.findDeptMainLeaders(deptId) ?? [] : await this.orgProv.findDeptLeaders(deptId) ?? [];
74
+ }
75
+ async deptIdOf(userId) {
76
+ if (!userId || !this.userProv)
77
+ return '';
78
+ const u = await this.userProv.getUser(userId);
79
+ return u?.deptId ?? '';
80
+ }
81
+ }
82
+ /** 当前用户(任务操作人)部门领导 */
83
+ export class DeptLeaderAssignmentHandler extends OrgBase {
84
+ async assign(_node, _inst, operator) {
85
+ return this.byDept(await this.deptIdOf(operator), false);
86
+ }
87
+ }
88
+ /** 当前用户(任务操作人)部门分管领导 */
89
+ export class DeptMainLeaderAssignmentHandler extends OrgBase {
90
+ async assign(_node, _inst, operator) {
91
+ return this.byDept(await this.deptIdOf(operator), true);
92
+ }
93
+ }
94
+ /** 发起人部门领导 */
95
+ export class ApplicantDeptLeaderAssignmentHandler extends OrgBase {
96
+ async assign(_node, inst, _operator) {
97
+ if (!inst)
98
+ return [];
99
+ return this.byDept(await this.deptIdOf(inst.operator), false);
100
+ }
101
+ }
102
+ /** 发起人部门分管领导 */
103
+ export class ApplicantDeptMainLeaderAssignmentHandler extends OrgBase {
104
+ async assign(_node, inst, _operator) {
105
+ if (!inst)
106
+ return [];
107
+ return this.byDept(await this.deptIdOf(inst.operator), true);
108
+ }
109
+ }
110
+ /** 任务节点唯一编码关联角色(roleCode = 节点 id) */
111
+ export class TaskRoleAssigneeHandler {
112
+ orgProv;
113
+ constructor(orgProv) {
114
+ this.orgProv = orgProv;
115
+ }
116
+ async assign(node, _inst, _operator) {
117
+ if (!node || !this.orgProv)
118
+ return [];
119
+ return await this.orgProv.findByRole(node.id) ?? [];
120
+ }
121
+ }
122
+ // ─── 注册 ──────────────────────────────────────────────────────────────────────
123
+ /**
124
+ * 注册内置通用参与者处理器到注册表(组织维度 handler 依赖 userProv/orgProv)。
125
+ * 与 HandlerRegistry.registerAssignment 组合使用。
126
+ */
127
+ export function registerBuiltinAssignments(reg, userProv, orgProv) {
128
+ reg.registerAssignment(HANDLER_OPERATOR_ASSIGNMENT, new OperatorAssignmentHandler());
129
+ reg.registerAssignment(HANDLER_FORM_FIELD_ASSIGNEE, new FormFieldAssigneeHandler());
130
+ reg.registerAssignment(HANDLER_DEPT_LEADER, new DeptLeaderAssignmentHandler(userProv, orgProv));
131
+ reg.registerAssignment(HANDLER_DEPT_MAIN_LEADER, new DeptMainLeaderAssignmentHandler(userProv, orgProv));
132
+ reg.registerAssignment(HANDLER_APPLICANT_DEPT_LEADER, new ApplicantDeptLeaderAssignmentHandler(userProv, orgProv));
133
+ reg.registerAssignment(HANDLER_APPLICANT_DEPT_MAIN_LEADER, new ApplicantDeptMainLeaderAssignmentHandler(userProv, orgProv));
134
+ reg.registerAssignment(HANDLER_TASK_ROLE_ASSIGNEE, new TaskRoleAssigneeHandler(orgProv));
135
+ }
package/dist/engine.d.ts CHANGED
@@ -11,6 +11,7 @@ export declare const KeyDeptName = "u_deptName";
11
11
  export declare const KeyPostID = "u_postId";
12
12
  export declare const KeyPostName = "u_postName";
13
13
  export declare const KeyNextNodeOperator = "tf_nextNodeOperator";
14
+ export declare const KeyProcessStartNextNodeOperator = "f_nextNodeOperator";
14
15
  export declare const KeyAutoExecute = "flow.auto";
15
16
  export declare const KeyAdminID = "flow.admin";
16
17
  export interface Engine {
package/dist/engine.js CHANGED
@@ -10,6 +10,8 @@ export const KeyPostID = 'u_postId';
10
10
  export const KeyPostName = 'u_postName';
11
11
  // v1.0.1:下一节点处理人(对齐 boot3 tf_nextNodeOperator)
12
12
  export const KeyNextNodeOperator = 'tf_nextNodeOperator';
13
+ // v1.6.0:流程启动时预指派人(对齐 boot3 f_nextNodeOperator)——startAndExecute 时转换为 tf_
14
+ export const KeyProcessStartNextNodeOperator = 'f_nextNodeOperator';
13
15
  // v1.0.1:系统代执行 / 超级管理员(对齐 boot3 FlowConst)
14
16
  export const KeyAutoExecute = 'flow.auto';
15
17
  export const KeyAdminID = 'flow.admin';
@@ -303,7 +305,7 @@ export class EngineImpl {
303
305
  }
304
306
  }
305
307
  async createTask(node, inst, operator, vars) {
306
- const actors = await this.resolveActors(node, inst, vars);
308
+ const actors = await this.resolveActors(node, inst, operator, vars);
307
309
  if (!actors.length)
308
310
  return;
309
311
  const performType = parseInt(String(node.properties?.performType ?? '0'));
@@ -338,14 +340,14 @@ export class EngineImpl {
338
340
  nt.actorIds = actors;
339
341
  await this.repo.saveTask(nt);
340
342
  }
341
- async resolveActors(node, inst, vars) {
343
+ async resolveActors(node, inst, operator, vars) {
342
344
  // 1a. Registry 按名称解析(推荐)
343
345
  if (this.registry) {
344
346
  const handlerName = node.properties?.assignmentHandler ?? '';
345
347
  if (handlerName) {
346
348
  const h = this.registry.resolveAssignment(handlerName);
347
349
  if (h)
348
- return await h.assign(node, inst);
350
+ return await h.assign(node, inst, operator);
349
351
  }
350
352
  }
351
353
  // 1b. Extensions 兼容
package/dist/facade.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ProcessExtRepository, ProcessRepository } from './spi.js';
1
+ import type { OrgUserProvider, ProcessExtRepository, ProcessRepository } from './spi.js';
2
2
  import type { EngineImpl } from './engine.js';
3
3
  export type UserSearch = (query: Record<string, any>) => Promise<[Record<string, any>[], number]> | [Record<string, any>[], number];
4
4
  export declare class JeeflowFacade {
@@ -6,8 +6,10 @@ export declare class JeeflowFacade {
6
6
  private readonly repo;
7
7
  private readonly extRepo?;
8
8
  private userSearch?;
9
+ private orgProv?;
9
10
  constructor(engine: EngineImpl, repo: ProcessRepository, extRepo?: ProcessExtRepository | undefined);
10
11
  setUserSearch(fn: UserSearch): this;
12
+ setOrgProvider(orgProv: OrgUserProvider): this;
11
13
  flow(action: string, args?: Record<string, any>): Promise<Record<string, any>>;
12
14
  private dispatch;
13
15
  private startAndExecute;
@@ -17,6 +19,12 @@ export declare class JeeflowFacade {
17
19
  private withdraw;
18
20
  private execute;
19
21
  private designPage;
22
+ /** 修改流程设计基本信息(对齐 boot3 ProcessDesignController.update,不写设计稿快照) */
23
+ private designUpdate;
24
+ /** 更新流程设计定义(设计稿保存,issues/08):content 快照入库 + 同步基本信息 + 置未部署 */
25
+ private designUpdateDefine;
26
+ /** 重新部署流程定义(issues/08):替换最新定义内容 + 置已部署(对齐 boot3 redeploy) */
27
+ private designRedeploy;
20
28
  private designDetail;
21
29
  private designSave;
22
30
  private surrogatePage;
package/dist/facade.js CHANGED
@@ -4,6 +4,7 @@
4
4
  // action(boot2/boot3 端点短名)路由。返回统一结构 {code, msg, data}
5
5
  // (code=0 成功 / 99999999 失败)。操作人约定:args.operator 显式传入。
6
6
  import { TaskState, } from './model.js';
7
+ import { KeyNextNodeOperator, KeyProcessStartNextNodeOperator } from './engine.js';
7
8
  // submitType 枚举(对齐 boot3)
8
9
  const SUBMIT_APPLY = 0;
9
10
  const SUBMIT_AGREE = 1;
@@ -17,6 +18,7 @@ export class JeeflowFacade {
17
18
  repo;
18
19
  extRepo;
19
20
  userSearch;
21
+ orgProv;
20
22
  constructor(engine, repo, extRepo) {
21
23
  this.engine = engine;
22
24
  this.repo = repo;
@@ -27,6 +29,11 @@ export class JeeflowFacade {
27
29
  this.userSearch = fn;
28
30
  return this;
29
31
  }
32
+ // 注入组织用户提供者(candidatePage candidateGroups 角色取人,v1.6.0)
33
+ setOrgProvider(orgProv) {
34
+ this.orgProv = orgProv;
35
+ return this;
36
+ }
30
37
  async flow(action, args = {}) {
31
38
  try {
32
39
  const data = await this.dispatch(action, args);
@@ -48,6 +55,8 @@ export class JeeflowFacade {
48
55
  case 'processDefine/deploy':
49
56
  case 'processDesign/deploy':
50
57
  return this.deploy(args, action === 'processDesign/deploy');
58
+ case 'processDesign/redeploy':
59
+ return this.designRedeploy(args);
51
60
  case 'processDefine/redeploy':
52
61
  return this.redeploy(args);
53
62
  case 'processDefine/remove':
@@ -72,6 +81,10 @@ export class JeeflowFacade {
72
81
  return this.designDetail(args);
73
82
  case 'processDesign/save':
74
83
  return this.designSave(args);
84
+ case 'processDesign/update':
85
+ return this.designUpdate(args);
86
+ case 'processDesign/updateDefine':
87
+ return this.designUpdateDefine(args);
75
88
  case 'processDesign/remove':
76
89
  return this.ext().removeDesign(toId(args.id));
77
90
  case 'processSurrogate/page':
@@ -125,6 +138,11 @@ export class JeeflowFacade {
125
138
  for (const task of doing) {
126
139
  await this.repo.addTaskActor(task.id, [operator]);
127
140
  flowArgs.submitType = SUBMIT_APPLY;
141
+ // 对齐 boot3:f_nextNodeOperator(发起时预指派人)→ tf_nextNodeOperator(引擎执行参数)
142
+ const startNextOp = flowArgs[KeyProcessStartNextNodeOperator];
143
+ if (startNextOp != null && String(startNextOp) !== '') {
144
+ flowArgs[KeyNextNodeOperator] = startNextOp;
145
+ }
128
146
  await this.engine.executeProcessTask(task.id, operator, flowArgs);
129
147
  }
130
148
  return { processInstanceId: inst.id };
@@ -247,6 +265,100 @@ export class JeeflowFacade {
247
265
  const [rows, total] = await this.ext().pageDesigns(toInt(args.pageNum ?? 1), toInt(args.pageSize ?? 10), undefined, parseMQuery(args));
248
266
  return { rows, recordCount: total };
249
267
  }
268
+ /** 修改流程设计基本信息(对齐 boot3 ProcessDesignController.update,不写设计稿快照) */
269
+ async designUpdate(args) {
270
+ const ext = this.ext();
271
+ const design = await ext.findDesignById(toId(args.id));
272
+ if (!design)
273
+ throw new Error('流程设计不存在');
274
+ if (args.name != null)
275
+ design.name = String(args.name);
276
+ if (args.displayName != null)
277
+ design.displayName = String(args.displayName);
278
+ if (args.type != null)
279
+ design.type = String(args.type);
280
+ if (args.icon != null)
281
+ design.icon = String(args.icon);
282
+ if (args.remark != null)
283
+ design.remark = String(args.remark);
284
+ design.updateUser = String(args.operator ?? 'system');
285
+ await ext.updateDesign(design);
286
+ return {};
287
+ }
288
+ /** 更新流程设计定义(设计稿保存,issues/08):content 快照入库 + 同步基本信息 + 置未部署 */
289
+ async designUpdateDefine(args) {
290
+ const ext = this.ext();
291
+ const designId = toId(args.processDesignId);
292
+ const design = await ext.findDesignById(designId);
293
+ if (!design)
294
+ throw new Error('流程设计不存在');
295
+ if (args.content == null)
296
+ throw new Error('content 缺失');
297
+ const content = toStr(args.content);
298
+ // 与最新一条相同则不重复入库(对齐 boot3 updateDefine)
299
+ const hisList = await ext.listDesignHis(designId);
300
+ if (hisList.length === 0 || toStr(hisList[0].content) !== content) {
301
+ await ext.saveDesignHis({
302
+ id: 0, processDesignId: designId, content,
303
+ createTime: new Date(), createUser: String(args.operator ?? 'system'),
304
+ });
305
+ }
306
+ // 同步设计基本信息(jsonObject 里的 name/displayName/type)+ 内容变更 → 未部署
307
+ try {
308
+ const flow = JSON.parse(content);
309
+ if (flow?.name)
310
+ design.name = flow.name;
311
+ if (flow?.displayName)
312
+ design.displayName = flow.displayName;
313
+ if (flow?.type)
314
+ design.type = flow.type;
315
+ }
316
+ catch { /* ignore */ }
317
+ design.isDeployed = 0;
318
+ design.updateUser = String(args.operator ?? 'system');
319
+ await ext.updateDesign(design);
320
+ return {};
321
+ }
322
+ /** 重新部署流程定义(issues/08):替换最新定义内容 + 置已部署(对齐 boot3 redeploy) */
323
+ async designRedeploy(args) {
324
+ const ext = this.ext();
325
+ const designId = toId(args.id);
326
+ const design = await ext.findDesignById(designId);
327
+ if (!design)
328
+ throw new Error('流程设计不存在');
329
+ const hisList = await ext.listDesignHis(designId);
330
+ if (hisList.length === 0)
331
+ throw new Error('流程设计没有内容,无法发布');
332
+ const content = toStr(hisList[0].content);
333
+ let flow;
334
+ try {
335
+ flow = JSON.parse(content);
336
+ }
337
+ catch (e) {
338
+ throw new Error('流程定义 JSON 解析失败: ' + String(e));
339
+ }
340
+ if (!flow?.name)
341
+ throw new Error('流程定义缺少 name');
342
+ // 按 name 取最新定义:有则替换内容(version 不变),无则新建(对齐 boot3 redeploy)
343
+ const last = await this.repo.findDefineByName(flow.name);
344
+ let defineId;
345
+ if (!last) {
346
+ defineId = await this.saveDeployedDefine(content, args);
347
+ }
348
+ else {
349
+ last.name = flow.name;
350
+ last.displayName = flow.displayName ?? '';
351
+ last.type = flow.type ?? '';
352
+ last.content = content;
353
+ last.updateUser = String(args.operator ?? 'system');
354
+ await this.repo.updateDefine(last);
355
+ defineId = last.id;
356
+ }
357
+ design.isDeployed = 1;
358
+ design.updateUser = String(args.operator ?? 'system');
359
+ await ext.updateDesign(design);
360
+ return { processDefineId: defineId };
361
+ }
250
362
  async designDetail(args) {
251
363
  const ext = this.ext();
252
364
  const design = await ext.findDesignById(toId(args.id));
@@ -307,6 +419,9 @@ export class JeeflowFacade {
307
419
  if (args.remark != null)
308
420
  found.remark = String(args.remark);
309
421
  found.updateUser = operator;
422
+ // 内容快照变更 → 置为未部署(对齐 boot3 updateDefine 语义,issues/08)
423
+ if (args.content != null)
424
+ found.isDeployed = 0;
310
425
  await ext.updateDesign(found);
311
426
  design = found;
312
427
  }
@@ -441,7 +556,8 @@ export class JeeflowFacade {
441
556
  return his.map(t => ({
442
557
  taskName: t.taskName, displayName: t.displayName, taskType: t.taskType ?? null,
443
558
  performType: t.performType ?? null, taskState: t.taskState, operator: t.actorId ?? '',
444
- finishTime: t.finishTime ?? null, variable: t.variables ?? {},
559
+ finishTime: fmtTime(t.finishTime), variable: t.variables ?? {},
560
+ ext: t.variables ?? {}, // issues/15:前端读 ext.tf_approvalComment
445
561
  }));
446
562
  }
447
563
  async getAssigneeTextData(args) {
@@ -491,6 +607,7 @@ export class JeeflowFacade {
491
607
  performType: task.performType ?? null, taskState: task.taskState,
492
608
  operator: task.actorId ?? '', formKey: task.formKey ?? '',
493
609
  taskActorIdList: actors, executable: task.isAllowed(operator),
610
+ taskFormData: formDataOf(task.variables, 'tf_'), // issues/15
494
611
  };
495
612
  // taskModel:流程定义中对应节点
496
613
  const inst = await this.repo.findInstanceById(task.processInstanceId);
@@ -535,13 +652,13 @@ export class JeeflowFacade {
535
652
  const inst = await this.repo.findInstanceById(task.processInstanceId);
536
653
  if (!inst)
537
654
  throw new Error('流程实例不存在');
538
- // 模型候选解析:后继任务节点的 candidateUsers 配置
655
+ // 模型候选解析:后继任务节点的 candidateUsers / candidateGroups 配置
539
656
  let candidates = [];
540
657
  const def = await this.repo.findDefineById(inst.defineId);
541
658
  if (def) {
542
659
  try {
543
660
  const flow = JSON.parse(toStr(def.content));
544
- candidates = this.nextTaskCandidates(flow, task.taskName);
661
+ candidates = await this.nextTaskCandidates(flow, task.taskName);
545
662
  }
546
663
  catch { /* ignore */ }
547
664
  }
@@ -555,10 +672,10 @@ export class JeeflowFacade {
555
672
  const [rows, total] = await this.userSearch(args);
556
673
  return { rows, recordCount: total };
557
674
  }
558
- nextTaskCandidates(flow, taskName) {
675
+ async nextTaskCandidates(flow, taskName) {
559
676
  const result = [];
560
677
  const visited = new Set();
561
- const collect = (node) => {
678
+ const collect = async (node) => {
562
679
  const v = node.properties?.candidateUsers;
563
680
  if (v) {
564
681
  for (const s of String(v).split(',')) {
@@ -567,8 +684,22 @@ export class JeeflowFacade {
567
684
  result.push(t);
568
685
  }
569
686
  }
687
+ // candidateGroups:按角色取人(v1.6.0,对齐 boot4 GlobalCandidateHandler)
688
+ const g = node.properties?.candidateGroups;
689
+ if (g && this.orgProv) {
690
+ for (const rc of String(g).split(',')) {
691
+ const role = rc.trim();
692
+ if (!role)
693
+ continue;
694
+ const ids = await this.orgProv.findByRole(role);
695
+ for (const uid of ids ?? []) {
696
+ if (uid && !result.includes(uid))
697
+ result.push(uid);
698
+ }
699
+ }
700
+ }
570
701
  };
571
- const walk = (nodeId) => {
702
+ const walk = async (nodeId) => {
572
703
  if (visited.has(nodeId))
573
704
  return;
574
705
  visited.add(nodeId);
@@ -579,15 +710,15 @@ export class JeeflowFacade {
579
710
  if (!target)
580
711
  continue;
581
712
  if (target.type === 'snaker:task' || target.type === 'snaker:custom') {
582
- collect(target);
713
+ await collect(target);
583
714
  continue;
584
715
  }
585
716
  if (['snaker:fork', 'snaker:join', 'snaker:decision'].includes(target.type)) {
586
- walk(target.id);
717
+ await walk(target.id);
587
718
  }
588
719
  }
589
720
  };
590
- walk(taskName);
721
+ await walk(taskName);
591
722
  return result;
592
723
  }
593
724
  async taskAddActor(args) {
@@ -657,6 +788,7 @@ export class JeeflowFacade {
657
788
  createTime: t.createTime, createUser: t.createUser,
658
789
  updateTime: t.updateTime, updateUser: t.updateUser,
659
790
  taskActorIdList: t.actorIds ?? [],
791
+ taskFormData: formDataOf(t.variables, 'tf_'), // issues/15
660
792
  };
661
793
  const ext = { ...(t.variables ?? {}) };
662
794
  const doing = t.taskState === TaskState.Doing;
@@ -666,15 +798,23 @@ export class JeeflowFacade {
666
798
  if (doing)
667
799
  activeTaskList.push(vo);
668
800
  }
669
- return {
801
+ const data = {
670
802
  id: inst.id, parentId: inst.parentId, processDefineId: inst.defineId,
671
803
  state: inst.state, parentNodeName: inst.parentNodeName,
672
804
  businessNo: inst.businessNo, operator: inst.operator,
673
- variables: inst.variables, createTime: inst.createTime, createUser: inst.createUser,
805
+ variables: inst.variables,
806
+ formData: formDataOf(inst.variables, 'f_'), // issues/15
807
+ createTime: inst.createTime, createUser: inst.createUser,
674
808
  jsonObject: graph, // issues/05
675
809
  tasks,
676
810
  activeTaskList,
677
811
  };
812
+ if (def0) {
813
+ data.displayName = def0.displayName; // issues/15
814
+ data.name = def0.name;
815
+ data.version = def0.version;
816
+ }
817
+ return data;
678
818
  }
679
819
  /** 流程 JSON 中第一个任务节点 id(issues/05-4 isFirstTaskNode 用) */
680
820
  firstTaskNodeId(graph) {
@@ -711,6 +851,17 @@ export class JeeflowFacade {
711
851
  }
712
852
  }
713
853
  // ── 行转 Map(issues/05-2 列表字段契约 + 05-3 时间格式)─────────────────────
854
+ /** issues/15:取 vars 中 prefix 前缀字段,输出「带前缀 + 去前缀副本」(对齐 boot3 getFormData) */
855
+ function formDataOf(vars, prefix) {
856
+ const out = {};
857
+ for (const [k, v] of Object.entries(vars ?? {})) {
858
+ if (k.startsWith(prefix)) {
859
+ out[k] = v;
860
+ out[k.slice(prefix.length)] = v;
861
+ }
862
+ }
863
+ return out;
864
+ }
714
865
  /** Date → 'yyyy-MM-dd HH:mm:ss'(null/undefined → null) */
715
866
  function fmtTime(v) {
716
867
  if (v == null)
@@ -779,6 +930,7 @@ function taskRowToMap(r) {
779
930
  processDefineName: r.processDefineName, processDefineDisplayName: r.processDefineDisplayName,
780
931
  instanceVariable: r.instanceVariable, instanceCreateTime: fmtTime(r.instanceCreateTime),
781
932
  ext, instanceExt, version: r.defineVersion,
933
+ taskFormData: formDataOf(r.variables, 'tf_'), // issues/15
782
934
  };
783
935
  }
784
936
  // ── 工具 ──────────────────────────────────────────────────────────────────────
package/dist/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export { MemoryRepository } from './memory.js';
3
3
  export { HandlerRegistry, type IAssignmentHandler, type IDecisionHandler, type HandlerMeta } from './registry.js';
4
4
  export { enumDict, enumDictKeys, type DictItem } from './metadata.js';
5
5
  export * from './model.js';
6
- export type { ProcessRepository, UserProvider, IDGenerator, ExpressionEvaluator } from './spi.js';
6
+ export type { ProcessRepository, UserProvider, OrgUserProvider, IDGenerator, ExpressionEvaluator } from './spi.js';
7
+ export { registerBuiltinAssignments, OperatorAssignmentHandler, FormFieldAssigneeHandler, DeptLeaderAssignmentHandler, DeptMainLeaderAssignmentHandler, ApplicantDeptLeaderAssignmentHandler, ApplicantDeptMainLeaderAssignmentHandler, TaskRoleAssigneeHandler, HANDLER_OPERATOR_ASSIGNMENT, HANDLER_FORM_FIELD_ASSIGNEE, HANDLER_DEPT_LEADER, HANDLER_DEPT_MAIN_LEADER, HANDLER_APPLICANT_DEPT_LEADER, HANDLER_APPLICANT_DEPT_MAIN_LEADER, HANDLER_TASK_ROLE_ASSIGNEE, } from './builtin.js';
package/dist/index.js CHANGED
@@ -3,3 +3,4 @@ export { MemoryRepository } from './memory.js';
3
3
  export { HandlerRegistry } from './registry.js';
4
4
  export { enumDict, enumDictKeys } from './metadata.js';
5
5
  export * from './model.js';
6
+ export { registerBuiltinAssignments, OperatorAssignmentHandler, FormFieldAssigneeHandler, DeptLeaderAssignmentHandler, DeptMainLeaderAssignmentHandler, ApplicantDeptLeaderAssignmentHandler, ApplicantDeptMainLeaderAssignmentHandler, TaskRoleAssigneeHandler, HANDLER_OPERATOR_ASSIGNMENT, HANDLER_FORM_FIELD_ASSIGNEE, HANDLER_DEPT_LEADER, HANDLER_DEPT_MAIN_LEADER, HANDLER_APPLICANT_DEPT_LEADER, HANDLER_APPLICANT_DEPT_MAIN_LEADER, HANDLER_TASK_ROLE_ASSIGNEE, } from './builtin.js';
@@ -1,7 +1,8 @@
1
1
  import type { FlowNode, ProcessInstance } from './model.js';
2
2
  /** 参与者指派处理器接口——对标 Java AssignmentHandler */
3
3
  export interface IAssignmentHandler {
4
- assign(node: FlowNode, inst: ProcessInstance): string[] | Promise<string[]>;
4
+ /** 返回参与者列表(operator: 当前任务操作人,issues/16 对齐 Java Execution.getOperator) */
5
+ assign(node: FlowNode, inst: ProcessInstance, operator: string): string[] | Promise<string[]>;
5
6
  }
6
7
  /** 决策处理器接口——对标 Java DecisionHandler */
7
8
  export interface IDecisionHandler {
package/dist/spi.d.ts CHANGED
@@ -49,6 +49,14 @@ export interface ProcessRepository {
49
49
  export interface UserProvider {
50
50
  getUser(userId: string): Promise<UserInfo | null>;
51
51
  }
52
+ export interface OrgUserProvider {
53
+ /** 部门领导(deptId → 领导 userId 列表) */
54
+ findDeptLeaders(deptId: string): Promise<string[]>;
55
+ /** 部门分管领导(deptId → 分管领导 userId 列表) */
56
+ findDeptMainLeaders(deptId: string): Promise<string[]>;
57
+ /** 按角色取人(roleCode → userId 列表) */
58
+ findByRole(roleCode: string): Promise<string[]>;
59
+ }
52
60
  export interface IDGenerator {
53
61
  nextId(): number;
54
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mldong/jeeflow",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "jeeflow workflow engine — Node.js / TypeScript implementation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",