@mldong/jeeflow 1.8.17 → 1.8.19
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.
- package/dist/builtin.d.ts +2 -1
- package/dist/builtin.js +4 -1
- package/dist/engine.d.ts +5 -0
- package/dist/engine.js +156 -59
- package/dist/facade.d.ts +7 -0
- package/dist/facade.js +88 -12
- package/dist/jdbc/ext.js +1 -2
- package/dist/memory-ext.js +1 -2
- package/package.json +1 -1
package/dist/builtin.d.ts
CHANGED
|
@@ -12,9 +12,10 @@ export declare const HANDLER_TASK_ROLE_ASSIGNEE: string;
|
|
|
12
12
|
export declare class OperatorAssignmentHandler implements IAssignmentHandler {
|
|
13
13
|
assign(_node: FlowNode, inst: ProcessInstance | null, _operator: string): Promise<string[]>;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
15
|
+
/** 按表单字段值分配参与者:f_ 前缀优先 → 裸名回落 → _数字 后缀去后缀再匹配。 */
|
|
16
16
|
export declare class FormFieldAssigneeHandler implements IAssignmentHandler {
|
|
17
17
|
assign(node: FlowNode, inst: ProcessInstance | null, _operator: string): Promise<string[]>;
|
|
18
|
+
/** issues/48:f_ 前缀优先 → 裸名回落 → 编号后缀去后缀匹配裸名 */
|
|
18
19
|
private findFieldValue;
|
|
19
20
|
private collect;
|
|
20
21
|
private add;
|
package/dist/builtin.js
CHANGED
|
@@ -21,7 +21,7 @@ export class OperatorAssignmentHandler {
|
|
|
21
21
|
return ['apply.operator'];
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
/**
|
|
24
|
+
/** 按表单字段值分配参与者:f_ 前缀优先 → 裸名回落 → _数字 后缀去后缀再匹配。 */
|
|
25
25
|
export class FormFieldAssigneeHandler {
|
|
26
26
|
async assign(node, inst, _operator) {
|
|
27
27
|
if (!inst || !node)
|
|
@@ -31,7 +31,10 @@ export class FormFieldAssigneeHandler {
|
|
|
31
31
|
return [];
|
|
32
32
|
return this.collect(value);
|
|
33
33
|
}
|
|
34
|
+
/** issues/48:f_ 前缀优先 → 裸名回落 → 编号后缀去后缀匹配裸名 */
|
|
34
35
|
findFieldValue(variables, fieldName) {
|
|
36
|
+
if ('f_' + fieldName in variables)
|
|
37
|
+
return variables['f_' + fieldName];
|
|
35
38
|
if (fieldName in variables)
|
|
36
39
|
return variables[fieldName];
|
|
37
40
|
const m = NUMBER_SUFFIX_PATTERN.exec(fieldName);
|
package/dist/engine.d.ts
CHANGED
|
@@ -49,6 +49,11 @@ export declare class EngineImpl implements Engine {
|
|
|
49
49
|
executeAndJumpToEnd(taskId: string, operator: string, args?: Record<string, any>): Promise<ProcessInstance>;
|
|
50
50
|
executeAndJumpTask(taskId: string, operator: string, args?: Record<string, any>, targetTaskName?: string): Promise<ProcessInstance>;
|
|
51
51
|
executeAndJumpToFirstTaskNode(taskId: string, operator: string, args?: Record<string, any>): Promise<ProcessInstance>;
|
|
52
|
+
private prepareExecuteTask;
|
|
53
|
+
private previousTaskName;
|
|
54
|
+
private isFirstTaskNode;
|
|
55
|
+
private rollbackActors;
|
|
56
|
+
private createTaskWithActors;
|
|
52
57
|
private loadAndCheck;
|
|
53
58
|
private executeNode;
|
|
54
59
|
private evaluateDecision;
|
package/dist/engine.js
CHANGED
|
@@ -127,31 +127,18 @@ export class EngineImpl {
|
|
|
127
127
|
}
|
|
128
128
|
// ─── Execute ───────────────────────────────────────────────────────────────
|
|
129
129
|
async executeProcessTask(taskId, operator, args = {}) {
|
|
130
|
-
const { task, inst } = await this.
|
|
131
|
-
// issues/26:办理提交的 f_ 字段按任务节点字段权限过滤(只读/隐藏不入变量)——
|
|
132
|
-
// 被拒值无法经流程变量落到下游节点写入,上游只读声明不可被绕过
|
|
133
|
-
const def = await this.repo.findDefineById(inst.defineId);
|
|
134
|
-
const flow = JSON.parse(typeof def.content === 'string' ? def.content : new TextDecoder().decode(def.content));
|
|
135
|
-
args = filterFieldByPerm(args, findNode(flow, task.taskName));
|
|
136
|
-
const vars = { ...inst.variables, ...task.variables, ...args };
|
|
137
|
-
await this.addUserInfo(operator, vars);
|
|
130
|
+
const { task, inst, flow, vars } = await this.prepareExecuteTask(taskId, operator, args);
|
|
138
131
|
const now = new Date();
|
|
139
|
-
// 聚合根:完成任务(子实体状态转换 + 实例变量合并)
|
|
140
|
-
inst.completeTask(task, operator, vars, now);
|
|
141
|
-
await this.repo.updateTask(task);
|
|
142
|
-
// v1.0.1:updateInstance 级联持久化依赖聚合内任务副本为最新状态,
|
|
143
|
-
// completeTask 改的是外部任务对象,需同步回聚合根
|
|
144
|
-
syncTaskToAggregate(inst, task);
|
|
145
|
-
await this.fireEvent({ type: EventType.TaskComplete, instanceId: inst.id, taskId: task.id, nodeId: task.taskName, operator });
|
|
146
|
-
inst.variables = vars;
|
|
147
|
-
await this.repo.updateInstance(inst);
|
|
148
132
|
const curNode = findNode(flow, task.taskName);
|
|
149
133
|
if (curNode) {
|
|
150
134
|
// 1.8.0:任务完成节点自身的后置拦截器(SYNC 同步演进——任务节点推进更新状态/字段)。
|
|
151
135
|
// createTask 不再触发(引擎语义修正),此处为完成任务节点的唯一触发点
|
|
152
136
|
await this.firePost(curNode, inst);
|
|
153
137
|
const ct = curNode.properties?.countersignType;
|
|
154
|
-
|
|
138
|
+
// issues/79:会签一票否决(对齐 Java CountersignHandler / PHP setMerged(true))——
|
|
139
|
+
// submitType=20 COUNTERSIGN_DISAGREE 时跳过会签"未完成即停留"门控,提前流转后续节点
|
|
140
|
+
const csVeto = !!ct && Number(vars[KeySubmitType]) === Number(SubmitType.CountersignDisagree);
|
|
141
|
+
if (ct === 'SEQUENTIAL' && !csVeto) {
|
|
155
142
|
const doing = await this.repo.findDoingTasks(inst.id);
|
|
156
143
|
if (doing.length === 0) {
|
|
157
144
|
const [actors, lc] = getCsState(vars, curNode.id);
|
|
@@ -171,7 +158,7 @@ export class EngineImpl {
|
|
|
171
158
|
return (await this.repo.findInstanceById(inst.id));
|
|
172
159
|
}
|
|
173
160
|
}
|
|
174
|
-
if (ct === 'PARALLEL' || ct?.startsWith('RATIO')) {
|
|
161
|
+
if ((ct === 'PARALLEL' || ct?.startsWith('RATIO')) && !csVeto) {
|
|
175
162
|
const doing = await this.repo.findDoingTasks(inst.id);
|
|
176
163
|
if (doing.length > 0)
|
|
177
164
|
return (await this.repo.findInstanceById(inst.id));
|
|
@@ -186,66 +173,176 @@ export class EngineImpl {
|
|
|
186
173
|
}
|
|
187
174
|
// ─── Reject ────────────────────────────────────────────────────────────────
|
|
188
175
|
async executeAndJumpToEnd(taskId, operator, args = {}) {
|
|
189
|
-
const {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
for (const t of inst.abandonAllDoing(now))
|
|
193
|
-
await this.repo.updateTask(t);
|
|
194
|
-
// 子实体:完成任务
|
|
195
|
-
task.finish(operator, task.variables, now);
|
|
196
|
-
await this.repo.updateTask(task);
|
|
197
|
-
// v1.0.1:同步回聚合根,避免 updateInstance 级联把任务写回旧状态
|
|
198
|
-
syncTaskToAggregate(inst, task);
|
|
199
|
-
// 聚合根:驳回
|
|
200
|
-
inst.reject(now);
|
|
176
|
+
const { inst } = await this.prepareExecuteTask(taskId, operator, args);
|
|
177
|
+
// 门面 submitType=2 REJECT 唯一入口(对齐 Java executeAndJumpToEnd 语义)
|
|
178
|
+
inst.reject(new Date());
|
|
201
179
|
await this.repo.updateInstance(inst);
|
|
202
180
|
await this.fireEvent({ type: EventType.ProcessReject, instanceId: inst.id, taskId, operator });
|
|
203
|
-
return inst;
|
|
181
|
+
return (await this.repo.findInstanceById(inst.id));
|
|
204
182
|
}
|
|
205
|
-
// ─── Jump
|
|
183
|
+
// ─── Jump(ROLLBACK 空 target / JUMP 命名 target,boot2 executeAndJumpTask)────
|
|
206
184
|
async executeAndJumpTask(taskId, operator, args = {}, targetTaskName) {
|
|
207
|
-
const { task, inst } = await this.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
185
|
+
const { task, inst, flow, vars } = await this.prepareExecuteTask(taskId, operator, args);
|
|
186
|
+
if (!targetTaskName) {
|
|
187
|
+
// issues/79:ROLLBACK 对齐 Java rejectTask——退回上一任务节点(首条输入边 source),
|
|
188
|
+
// 新任务 actor=当前任务完成人(退回操作人);无上一任务节点则不产生新待办
|
|
189
|
+
const prevName = this.previousTaskName(flow, task.taskName);
|
|
190
|
+
if (prevName) {
|
|
191
|
+
const prev = findNode(flow, prevName);
|
|
192
|
+
if (prev) {
|
|
193
|
+
const actors = this.rollbackActors(prev, inst, task);
|
|
194
|
+
await this.createTaskWithActors(prev, inst, operator, vars, actors);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// issues/79:对齐 Java——目标节点不存在显式报错(前端 JUMP 无效 taskName 不再静默空操作)
|
|
218
200
|
const target = findNode(flow, targetTaskName);
|
|
219
|
-
if (target)
|
|
220
|
-
|
|
201
|
+
if (!target)
|
|
202
|
+
throw new Error(`根据节点名称[${targetTaskName}]无法找到节点模型`);
|
|
203
|
+
// 对齐 Java isFirstTaskName:跳首任务节点(start 直接后继)assignee 强制为发起人
|
|
204
|
+
if (target.type === TypeTask && this.isFirstTaskNode(flow, target)) {
|
|
205
|
+
target.properties = target.properties ?? {};
|
|
206
|
+
target.properties.assignee = inst.operator;
|
|
207
|
+
}
|
|
208
|
+
await this.executeNode(flow, inst, target, operator, vars);
|
|
221
209
|
}
|
|
222
|
-
return inst;
|
|
210
|
+
return (await this.repo.findInstanceById(inst.id));
|
|
223
211
|
}
|
|
224
212
|
// ─── Jump To First Task(退回发起人,boot2 ROLLBACK_TO_OPERATOR=6)────────────
|
|
225
213
|
async executeAndJumpToFirstTaskNode(taskId, operator, args = {}) {
|
|
226
|
-
const {
|
|
227
|
-
const now = new Date();
|
|
228
|
-
// 聚合根:废弃所有进行中任务
|
|
229
|
-
for (const t of inst.abandonAllDoing(now))
|
|
230
|
-
await this.repo.updateTask(t);
|
|
231
|
-
// 子实体:完成任务
|
|
232
|
-
task.finish(operator, task.variables, now);
|
|
233
|
-
await this.repo.updateTask(task);
|
|
214
|
+
const { inst, flow, vars } = await this.prepareExecuteTask(taskId, operator, args);
|
|
234
215
|
// 找到第一个任务节点,强制参与者为发起人,重新执行
|
|
235
|
-
const def = await this.repo.findDefineById(inst.defineId);
|
|
236
|
-
const flow = JSON.parse(typeof def.content === 'string' ? def.content : new TextDecoder().decode(def.content));
|
|
237
216
|
const startNode = findNodeByType(flow, TypeStart);
|
|
238
217
|
if (startNode) {
|
|
239
218
|
for (const node of followEdges(flow, startNode.id)) {
|
|
240
219
|
if (node.type === TypeTask || node.type === TypeCustom) {
|
|
241
220
|
node.properties = node.properties ?? {};
|
|
242
221
|
node.properties.assignee = inst.operator;
|
|
243
|
-
await this.executeNode(flow, inst, node, operator,
|
|
222
|
+
await this.executeNode(flow, inst, node, operator, vars);
|
|
244
223
|
break;
|
|
245
224
|
}
|
|
246
225
|
}
|
|
247
226
|
}
|
|
248
|
-
return inst;
|
|
227
|
+
return (await this.repo.findInstanceById(inst.id));
|
|
228
|
+
}
|
|
229
|
+
// ─── Execute 公共序言(对齐 Java prepareExecution)──────────────────────────
|
|
230
|
+
// 执行公共序言(对齐 Java prepareExecution):权限校验 → f_ 字段权限过滤 →
|
|
231
|
+
// 完成任务(子实体状态转换 + 实例变量合并,经 updateInstance 级联落库)→
|
|
232
|
+
// 返回流程模型 + 合并后执行变量。Java jump 路径不废弃其余 DOING 任务
|
|
233
|
+
// (会签兄弟任务不受影响),此处保持一致。
|
|
234
|
+
async prepareExecuteTask(taskId, operator, args) {
|
|
235
|
+
const { task, inst } = await this.loadAndCheck(taskId, operator);
|
|
236
|
+
// issues/26:办理提交的 f_ 字段按任务节点字段权限过滤(只读/隐藏不入变量)
|
|
237
|
+
const def = await this.repo.findDefineById(inst.defineId);
|
|
238
|
+
const flow = JSON.parse(typeof def.content === 'string' ? def.content : new TextDecoder().decode(def.content));
|
|
239
|
+
args = filterFieldByPerm(args, findNode(flow, task.taskName));
|
|
240
|
+
const vars = { ...inst.variables, ...task.variables, ...args };
|
|
241
|
+
await this.addUserInfo(operator, vars);
|
|
242
|
+
const now = new Date();
|
|
243
|
+
// 聚合根:完成任务(子实体状态转换 + 实例变量合并)
|
|
244
|
+
inst.completeTask(task, operator, vars, now);
|
|
245
|
+
await this.repo.updateTask(task);
|
|
246
|
+
// v1.0.1:updateInstance 级联持久化依赖聚合内任务副本为最新状态,
|
|
247
|
+
// completeTask 改的是外部任务对象,需同步回聚合根
|
|
248
|
+
syncTaskToAggregate(inst, task);
|
|
249
|
+
await this.fireEvent({ type: EventType.TaskComplete, instanceId: inst.id, taskId: task.id, nodeId: task.taskName, operator });
|
|
250
|
+
inst.variables = vars;
|
|
251
|
+
await this.repo.updateInstance(inst);
|
|
252
|
+
return { task, inst, flow, vars };
|
|
253
|
+
}
|
|
254
|
+
// 当前任务节点的首条输入边 source(issues/79 对齐 Java getPreviousTaskName)
|
|
255
|
+
previousTaskName(flow, taskName) {
|
|
256
|
+
const node = findNode(flow, taskName);
|
|
257
|
+
if (!node)
|
|
258
|
+
return '';
|
|
259
|
+
for (const edge of flow.edges) {
|
|
260
|
+
if (edge.targetNodeId === node.id) {
|
|
261
|
+
const src = findNode(flow, edge.sourceNodeId);
|
|
262
|
+
if (src && (src.type === TypeTask || src.type === TypeCustom))
|
|
263
|
+
return src.id;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return '';
|
|
267
|
+
}
|
|
268
|
+
// 是否 start 直接后继任务节点(issues/79 对齐 Java FlowUtil.isFirstTaskName)
|
|
269
|
+
isFirstTaskNode(flow, node) {
|
|
270
|
+
const start = findNodeByType(flow, TypeStart);
|
|
271
|
+
if (!start)
|
|
272
|
+
return false;
|
|
273
|
+
return flow.edges.some(e => e.sourceNodeId === start.id && e.targetNodeId === node.id);
|
|
274
|
+
}
|
|
275
|
+
// ROLLBACK 新任务参与者:优先当前任务完成人(退回操作人,对齐 Java rejectTask
|
|
276
|
+
// singletonList(currentTask.getActorId())),其次按目标节点 assignee 解析
|
|
277
|
+
rollbackActors(node, inst, task) {
|
|
278
|
+
if (task.actorId)
|
|
279
|
+
return [task.actorId];
|
|
280
|
+
const nextOp = inst.variables[KeyNextNodeOperator];
|
|
281
|
+
if (nextOp != null) {
|
|
282
|
+
if (typeof nextOp === 'string')
|
|
283
|
+
return nextOp.split(',').map(s => s.trim()).filter(Boolean);
|
|
284
|
+
if (Array.isArray(nextOp))
|
|
285
|
+
return nextOp.map(String);
|
|
286
|
+
return [String(nextOp)];
|
|
287
|
+
}
|
|
288
|
+
const assignee = node.properties?.assignee;
|
|
289
|
+
if (assignee) {
|
|
290
|
+
const actors = [];
|
|
291
|
+
for (const raw of assignee.split(',')) {
|
|
292
|
+
let token = raw.trim();
|
|
293
|
+
if (!token)
|
|
294
|
+
continue;
|
|
295
|
+
if (token.includes('applicant'))
|
|
296
|
+
token = token.replace('applicant', inst.operator);
|
|
297
|
+
if (token in inst.variables) {
|
|
298
|
+
const val = inst.variables[token];
|
|
299
|
+
if (Array.isArray(val))
|
|
300
|
+
actors.push(...val.map(String));
|
|
301
|
+
else
|
|
302
|
+
actors.push(String(val));
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
actors.push(token);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return actors;
|
|
309
|
+
}
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
// 以显式参与者建任务(会签节点拆分为逐人任务,对齐 Java 会签创建语义)
|
|
313
|
+
async createTaskWithActors(node, inst, operator, vars, actors) {
|
|
314
|
+
if (!actors.length)
|
|
315
|
+
return;
|
|
316
|
+
const ct = node.properties?.countersignType;
|
|
317
|
+
const now = new Date();
|
|
318
|
+
const form = node.properties?.form ?? '';
|
|
319
|
+
if (isCountersign(node.properties?.performType) && ct) {
|
|
320
|
+
switch (ct) {
|
|
321
|
+
case 'PARALLEL':
|
|
322
|
+
case '':
|
|
323
|
+
for (const actor of actors)
|
|
324
|
+
await this.repo.saveTask(inst.createTask(this.nextId(), node.id, node.text.value, actor, operator, form, now, 1));
|
|
325
|
+
return;
|
|
326
|
+
case 'SEQUENTIAL': {
|
|
327
|
+
const nt = inst.createTask(this.nextId(), node.id, node.text.value, actors[0], operator, form, now, 1);
|
|
328
|
+
nt.variables = {
|
|
329
|
+
[`nrOfInstances_${node.id}`]: actors.length,
|
|
330
|
+
[`loopCounter_${node.id}`]: 0,
|
|
331
|
+
[`operatorList_${node.id}`]: actors,
|
|
332
|
+
};
|
|
333
|
+
await this.repo.saveTask(nt);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
default:
|
|
337
|
+
for (const actor of actors)
|
|
338
|
+
await this.repo.saveTask(inst.createTask(this.nextId(), node.id, node.text.value, actor, operator, form, now, 1));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const nt = inst.createTask(this.nextId(), node.id, node.text.value, actors[0], operator, form, now);
|
|
343
|
+
if (actors.length > 1)
|
|
344
|
+
nt.actorIds = actors;
|
|
345
|
+
await this.repo.saveTask(nt);
|
|
249
346
|
}
|
|
250
347
|
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
251
348
|
async loadAndCheck(taskId, operator) {
|
package/dist/facade.d.ts
CHANGED
|
@@ -44,6 +44,13 @@ export declare class JeeflowFacade {
|
|
|
44
44
|
private designSave;
|
|
45
45
|
private surrogatePage;
|
|
46
46
|
private surrogateSave;
|
|
47
|
+
/** 委托更新(issues/77):按 id 全字段更新,id 缺失/不存在报错 */
|
|
48
|
+
private surrogateUpdate;
|
|
49
|
+
/** 委托详情(issues/77):按 id 查单条,返回行结构(时间格式化) */
|
|
50
|
+
private surrogateDetail;
|
|
51
|
+
/** 委托写入公共字段。授权人(operator)仅在显式传入时覆盖,避免 update
|
|
52
|
+
* 时清空原授权人(前端编辑表单不带 operator;集成层注入时 operator=授权人,覆盖无害) */
|
|
53
|
+
private applySurrogateFields;
|
|
47
54
|
private getLastByName;
|
|
48
55
|
private highLight;
|
|
49
56
|
/** 节点成员进度(issue 41,对齐 boot3 highLight):按任务状态 + 会签变量组装
|
package/dist/facade.js
CHANGED
|
@@ -101,6 +101,10 @@ export class JeeflowFacade {
|
|
|
101
101
|
return this.surrogatePage(args);
|
|
102
102
|
case 'processSurrogate/save':
|
|
103
103
|
return this.surrogateSave(args);
|
|
104
|
+
case 'processSurrogate/update': // issues/77
|
|
105
|
+
return this.surrogateUpdate(args);
|
|
106
|
+
case 'processSurrogate/detail': // issues/77
|
|
107
|
+
return this.surrogateDetail(args);
|
|
104
108
|
case 'processSurrogate/remove':
|
|
105
109
|
return this.ext().removeSurrogate(toId(args.id));
|
|
106
110
|
case 'processDefine/getLastByName':
|
|
@@ -576,7 +580,8 @@ export class JeeflowFacade {
|
|
|
576
580
|
const pageNum = toInt(args.pageNum ?? 1);
|
|
577
581
|
const pageSize = toInt(args.pageSize ?? 10);
|
|
578
582
|
const [rows, total] = await this.ext().pageSurrogates(pageNum, pageSize, filters, parseMQuery(args));
|
|
579
|
-
|
|
583
|
+
// issues/77:行走 surrogateRowToMap(时间格式化),与 detail 同构
|
|
584
|
+
return pageData(pageNum, pageSize, total, rows.map(r => surrogateRowToMap(r)));
|
|
580
585
|
}
|
|
581
586
|
async surrogateSave(args) {
|
|
582
587
|
const ext = this.ext();
|
|
@@ -585,30 +590,56 @@ export class JeeflowFacade {
|
|
|
585
590
|
let surrogate;
|
|
586
591
|
if (!surrogateId) {
|
|
587
592
|
surrogate = {
|
|
588
|
-
id: '', operator, // 授权人 =
|
|
589
|
-
|
|
590
|
-
enabled: toInt(args.enabled ?? 1),
|
|
593
|
+
id: '', operator, surrogate: '', processName: '', // 授权人 = 操作人(新建必有)
|
|
594
|
+
enabled: 1,
|
|
591
595
|
createTime: new Date(), createUser: operator,
|
|
592
596
|
updateTime: new Date(), updateUser: operator,
|
|
593
597
|
};
|
|
598
|
+
this.applySurrogateFields(surrogate, args, operator);
|
|
594
599
|
await ext.saveSurrogate(surrogate);
|
|
595
600
|
}
|
|
596
601
|
else {
|
|
597
602
|
const found = await ext.findSurrogateById(surrogateId);
|
|
598
603
|
if (!found)
|
|
599
604
|
throw new Error('委托记录不存在');
|
|
600
|
-
|
|
601
|
-
found.surrogate = String(args.surrogate);
|
|
602
|
-
if (args.processName != null)
|
|
603
|
-
found.processName = String(args.processName);
|
|
604
|
-
if (args.enabled != null)
|
|
605
|
-
found.enabled = toInt(args.enabled);
|
|
606
|
-
found.updateUser = operator;
|
|
605
|
+
this.applySurrogateFields(found, args, operator);
|
|
607
606
|
await ext.updateSurrogate(found);
|
|
608
607
|
surrogate = found;
|
|
609
608
|
}
|
|
610
609
|
return { id: surrogate.id };
|
|
611
610
|
}
|
|
611
|
+
/** 委托更新(issues/77):按 id 全字段更新,id 缺失/不存在报错 */
|
|
612
|
+
async surrogateUpdate(args) {
|
|
613
|
+
const ext = this.ext();
|
|
614
|
+
const surrogateId = toId(args.id);
|
|
615
|
+
const surrogate = await ext.findSurrogateById(surrogateId);
|
|
616
|
+
if (!surrogate)
|
|
617
|
+
throw new Error('委托记录不存在');
|
|
618
|
+
const operator = String(args.operator ?? 'user1');
|
|
619
|
+
this.applySurrogateFields(surrogate, args, operator);
|
|
620
|
+
await ext.updateSurrogate(surrogate);
|
|
621
|
+
return { id: surrogate.id };
|
|
622
|
+
}
|
|
623
|
+
/** 委托详情(issues/77):按 id 查单条,返回行结构(时间格式化) */
|
|
624
|
+
async surrogateDetail(args) {
|
|
625
|
+
const surrogateId = toId(args.id);
|
|
626
|
+
const surrogate = await this.ext().findSurrogateById(surrogateId);
|
|
627
|
+
if (!surrogate)
|
|
628
|
+
throw new Error('委托记录不存在');
|
|
629
|
+
return surrogateRowToMap(surrogate);
|
|
630
|
+
}
|
|
631
|
+
/** 委托写入公共字段。授权人(operator)仅在显式传入时覆盖,避免 update
|
|
632
|
+
* 时清空原授权人(前端编辑表单不带 operator;集成层注入时 operator=授权人,覆盖无害) */
|
|
633
|
+
applySurrogateFields(s, args, operator) {
|
|
634
|
+
s.processName = String(args.processName ?? '');
|
|
635
|
+
if ('operator' in args)
|
|
636
|
+
s.operator = String(args.operator);
|
|
637
|
+
s.surrogate = String(args.surrogate ?? '');
|
|
638
|
+
s.startTime = parseSurrogateTime(args.startTime);
|
|
639
|
+
s.endTime = parseSurrogateTime(args.endTime);
|
|
640
|
+
s.enabled = args.enabled != null ? toInt(args.enabled) : 1;
|
|
641
|
+
s.updateUser = operator;
|
|
642
|
+
}
|
|
612
643
|
// ── 视图端点(v1.2.0) ──────────────────────────────────────────────────
|
|
613
644
|
async getLastByName(args) {
|
|
614
645
|
const def = await this.repo.findDefineByName(String(args.processDefineName ?? ''));
|
|
@@ -805,12 +836,18 @@ export class JeeflowFacade {
|
|
|
805
836
|
if (!task)
|
|
806
837
|
throw new Error('任务不存在');
|
|
807
838
|
const actors = await this.repo.findTaskActors(taskId);
|
|
839
|
+
// issues/82-5:任务级 ext.isFirstTaskNode(前端 detail.vue 双兜底 record.ext?.isFirstTaskNode)
|
|
840
|
+
// 首个任务节点且 DOING → true,与 instance detail 的 activeTaskList 行语义一致
|
|
841
|
+
const tExt = { ...(task.variables ?? {}) };
|
|
842
|
+
const doing = task.taskState === TaskState.Doing;
|
|
843
|
+
tExt.isFirstTaskNode = false;
|
|
808
844
|
const vo = {
|
|
809
845
|
id: task.id, processInstanceId: task.processInstanceId, taskName: task.taskName,
|
|
810
846
|
displayName: task.displayName, taskType: task.taskType ?? null,
|
|
811
847
|
performType: task.performType ?? null, taskState: task.taskState,
|
|
812
848
|
operator: task.actorId ?? '', formKey: task.formKey ?? '',
|
|
813
849
|
taskActorIdList: actors, executable: task.isAllowed(operator),
|
|
850
|
+
ext: tExt,
|
|
814
851
|
taskFormData: formDataOf(task.variables, 'tf_'), // issues/15
|
|
815
852
|
};
|
|
816
853
|
// taskModel:流程定义中对应节点
|
|
@@ -819,6 +856,7 @@ export class JeeflowFacade {
|
|
|
819
856
|
const def = await this.repo.findDefineById(inst.defineId);
|
|
820
857
|
if (def) {
|
|
821
858
|
vo.jsonObject = this.parseGraph(def.content); // issues/05
|
|
859
|
+
tExt.isFirstTaskNode = doing && task.taskName === this.firstTaskNodeId(vo.jsonObject);
|
|
822
860
|
try {
|
|
823
861
|
const flow = JSON.parse(toStr(def.content));
|
|
824
862
|
for (const n of flow.nodes ?? []) {
|
|
@@ -874,7 +912,8 @@ export class JeeflowFacade {
|
|
|
874
912
|
catch { /* ignore */ }
|
|
875
913
|
}
|
|
876
914
|
if (candidates.length > 0) {
|
|
877
|
-
|
|
915
|
+
// issues/80:行键对齐前端 UserSelect(valueField='id')——补 id 键,保留 userId 兼容旧消费方
|
|
916
|
+
const rows = candidates.map(c => ({ id: c, userId: c, realName: c }));
|
|
878
917
|
return pageData(pageNum, pageSize, rows.length, rows);
|
|
879
918
|
}
|
|
880
919
|
// 无模型候选 → 用户分页搜索(依赖 userSearch 钩子)
|
|
@@ -1102,6 +1141,37 @@ function defineRowToMap(r) {
|
|
|
1102
1141
|
};
|
|
1103
1142
|
}
|
|
1104
1143
|
/** 设计行:时间格式化(issues/63) */
|
|
1144
|
+
/** 委托行:时间格式化(issues/77,对齐 Java surrogateRowToMap / Go surrogateRowToMap / SPEC) */
|
|
1145
|
+
function surrogateRowToMap(s) {
|
|
1146
|
+
return {
|
|
1147
|
+
id: s.id, processName: s.processName, operator: s.operator, surrogate: s.surrogate,
|
|
1148
|
+
startTime: fmtTime(s.startTime ?? null), endTime: fmtTime(s.endTime ?? null),
|
|
1149
|
+
enabled: s.enabled,
|
|
1150
|
+
createTime: fmtTime(s.createTime ?? null), createUser: s.createUser,
|
|
1151
|
+
updateTime: fmtTime(s.updateTime ?? null), updateUser: s.updateUser,
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
/** 解析委托时间入参:兼容 yyyy-MM-dd HH:mm:ss(前端 RangePicker/SPEC 契约)与 ISO T(issues/77) */
|
|
1155
|
+
function parseSurrogateTime(v) {
|
|
1156
|
+
if (v == null)
|
|
1157
|
+
return undefined;
|
|
1158
|
+
const s = String(v).trim();
|
|
1159
|
+
if (!s)
|
|
1160
|
+
return undefined;
|
|
1161
|
+
for (const fmt of ['YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DDTHH:mm:ss', 'YYYY-MM-DD']) {
|
|
1162
|
+
const m = s.match(fmt === 'YYYY-MM-DD'
|
|
1163
|
+
? /^(\d{4})-(\d{2})-(\d{2})$/
|
|
1164
|
+
: /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})$/);
|
|
1165
|
+
if (!m)
|
|
1166
|
+
continue;
|
|
1167
|
+
if (fmt === 'YYYY-MM-DD')
|
|
1168
|
+
return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
1169
|
+
return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), Number(m[4]), Number(m[5]), Number(m[6]));
|
|
1170
|
+
}
|
|
1171
|
+
// 兜底:交给 Date 解析(覆盖其它可解析形态),失败返回 undefined
|
|
1172
|
+
const d = new Date(s);
|
|
1173
|
+
return isNaN(d.getTime()) ? undefined : d;
|
|
1174
|
+
}
|
|
1105
1175
|
function designRowToMap(r) {
|
|
1106
1176
|
return {
|
|
1107
1177
|
id: r.id, name: r.name, displayName: r.displayName,
|
|
@@ -1199,6 +1269,12 @@ function toId(v) {
|
|
|
1199
1269
|
// 数字输入(JS 安全整数内)转字符串;字符串输入原样保留(含超长雪花 id)。
|
|
1200
1270
|
if (v == null)
|
|
1201
1271
|
throw new Error('id 缺失或非法');
|
|
1272
|
+
// issues/82 负向(对齐 Go TestSnowflakeIDPrecision / issues/38 E9):数字 id 超 2^53
|
|
1273
|
+
// 说明它已被 JSON 解析器(JSON.parse / encoding/json)降级为 float64 且精度已丢,
|
|
1274
|
+
// 必须显性报错而非 String() 静默截断成错误 id。
|
|
1275
|
+
if (typeof v === 'number' && Math.abs(v) > 2 ** 53) {
|
|
1276
|
+
throw new Error(`id ${v} 超出 float64 精确范围(2^53),请以字符串传递`);
|
|
1277
|
+
}
|
|
1202
1278
|
const s = String(v).trim();
|
|
1203
1279
|
if (!/^\d+$/.test(s) || s === '0')
|
|
1204
1280
|
throw new Error('id 缺失或非法');
|
package/dist/jdbc/ext.js
CHANGED
|
@@ -203,8 +203,7 @@ export class JdbcProcessExtRepository {
|
|
|
203
203
|
s.createTime = now;
|
|
204
204
|
if (!s.updateTime)
|
|
205
205
|
s.updateTime = now;
|
|
206
|
-
|
|
207
|
-
s.enabled = 1;
|
|
206
|
+
// 显式 enabled=0 是合法值(停用委托);缺省由门面处理(对齐 Java/Go/Python,issues/82-7)
|
|
208
207
|
const conn = await this.c();
|
|
209
208
|
try {
|
|
210
209
|
await conn.execute(this.sql('INSERT INTO wf_process_surrogate (id, process_name, operator, surrogate, start_time, ' +
|
package/dist/memory-ext.js
CHANGED
|
@@ -71,8 +71,7 @@ export class MemoryExtRepository {
|
|
|
71
71
|
s.createTime = now;
|
|
72
72
|
if (!s.updateTime)
|
|
73
73
|
s.updateTime = now;
|
|
74
|
-
|
|
75
|
-
s.enabled = 1;
|
|
74
|
+
// 显式 enabled=0 是合法值(停用委托);缺省由门面处理(对齐 Java/Go/Python,issues/82-7)
|
|
76
75
|
this.surrogates.set(s.id, { ...s });
|
|
77
76
|
}
|
|
78
77
|
async updateSurrogate(s) {
|