@rytass/bpm-core-nestjs-module 0.1.10 → 0.3.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.
- package/CHANGELOG.md +92 -0
- package/README.md +43 -0
- package/package.json +3 -2
- package/src/lib/database/migration-runner.js +2 -0
- package/src/lib/database/migration-runner.js.map +1 -1
- package/src/lib/database/reconcile-legacy-migrations.d.ts +13 -0
- package/src/lib/database/reconcile-legacy-migrations.js +70 -0
- package/src/lib/database/reconcile-legacy-migrations.js.map +1 -0
- package/src/lib/form/dto/form-definition.input.d.ts +5 -0
- package/src/lib/form/dto/form-definition.input.js +24 -1
- package/src/lib/form/dto/form-definition.input.js.map +1 -1
- package/src/lib/form/form.mutations.d.ts +2 -2
- package/src/lib/form/form.mutations.js +6 -5
- package/src/lib/form/form.mutations.js.map +1 -1
- package/src/lib/form/form.service.d.ts +20 -6
- package/src/lib/form/form.service.js +129 -80
- package/src/lib/form/form.service.js.map +1 -1
- package/src/lib/migrations/0000000014000-notification-resolution.d.ts +6 -0
- package/src/lib/migrations/0000000014000-notification-resolution.js +30 -0
- package/src/lib/migrations/0000000014000-notification-resolution.js.map +1 -0
- package/src/lib/migrations/0000000015000-backfill-stale-notification-resolution.d.ts +15 -0
- package/src/lib/migrations/0000000015000-backfill-stale-notification-resolution.js +37 -0
- package/src/lib/migrations/0000000015000-backfill-stale-notification-resolution.js.map +1 -0
- package/src/lib/migrations/0000000016000-archive-parallel-form-drafts.d.ts +15 -0
- package/src/lib/migrations/0000000016000-archive-parallel-form-drafts.js +34 -0
- package/src/lib/migrations/0000000016000-archive-parallel-form-drafts.js.map +1 -0
- package/src/lib/migrations/0000000017000-adhoc-directives.d.ts +6 -0
- package/src/lib/migrations/0000000017000-adhoc-directives.js +53 -0
- package/src/lib/migrations/0000000017000-adhoc-directives.js.map +1 -0
- package/src/lib/migrations/index.d.ts +4 -0
- package/src/lib/migrations/index.js +12 -0
- package/src/lib/migrations/index.js.map +1 -1
- package/src/lib/notification/notification.entity.d.ts +9 -1
- package/src/lib/notification/notification.entity.js +27 -0
- package/src/lib/notification/notification.entity.js.map +1 -1
- package/src/lib/notification/notification.enums.d.ts +14 -0
- package/src/lib/notification/notification.enums.js +19 -1
- package/src/lib/notification/notification.enums.js.map +1 -1
- package/src/lib/notification/notification.service.d.ts +40 -0
- package/src/lib/notification/notification.service.js +92 -0
- package/src/lib/notification/notification.service.js.map +1 -1
- package/src/lib/template/compose-approval-template.object.d.ts +11 -0
- package/src/lib/template/compose-approval-template.object.js +36 -0
- package/src/lib/template/compose-approval-template.object.js.map +1 -0
- package/src/lib/template/compose-template.mutations.d.ts +8 -0
- package/src/lib/template/compose-template.mutations.js +32 -0
- package/src/lib/template/compose-template.mutations.js.map +1 -0
- package/src/lib/template/dto/compose-approval-template.input.d.ts +17 -0
- package/src/lib/template/dto/compose-approval-template.input.js +99 -0
- package/src/lib/template/dto/compose-approval-template.input.js.map +1 -0
- package/src/lib/template/index.d.ts +2 -0
- package/src/lib/template/index.js +2 -0
- package/src/lib/template/index.js.map +1 -1
- package/src/lib/template/template.module.js +9 -1
- package/src/lib/template/template.module.js.map +1 -1
- package/src/lib/template/template.service.d.ts +25 -6
- package/src/lib/template/template.service.js +216 -83
- package/src/lib/template/template.service.js.map +1 -1
- package/src/lib/workflow-engine/adhoc-directive.entity.d.ts +31 -0
- package/src/lib/workflow-engine/adhoc-directive.entity.js +105 -0
- package/src/lib/workflow-engine/adhoc-directive.entity.js.map +1 -0
- package/src/lib/workflow-engine/adhoc.enums.d.ts +21 -0
- package/src/lib/workflow-engine/adhoc.enums.js +42 -0
- package/src/lib/workflow-engine/adhoc.enums.js.map +1 -0
- package/src/lib/workflow-engine/dto/adhoc-notification.input.d.ts +6 -0
- package/src/lib/workflow-engine/dto/adhoc-notification.input.js +28 -0
- package/src/lib/workflow-engine/dto/adhoc-notification.input.js.map +1 -0
- package/src/lib/workflow-engine/dto/adhoc-target.input.d.ts +16 -0
- package/src/lib/workflow-engine/dto/adhoc-target.input.js +61 -0
- package/src/lib/workflow-engine/dto/adhoc-target.input.js.map +1 -0
- package/src/lib/workflow-engine/index.d.ts +4 -0
- package/src/lib/workflow-engine/index.js +4 -0
- package/src/lib/workflow-engine/index.js.map +1 -1
- package/src/lib/workflow-engine/task.entity.d.ts +5 -0
- package/src/lib/workflow-engine/task.entity.js +21 -0
- package/src/lib/workflow-engine/task.entity.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.enums.d.ts +3 -0
- package/src/lib/workflow-engine/workflow-engine.enums.js +3 -0
- package/src/lib/workflow-engine/workflow-engine.enums.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.module.js +2 -0
- package/src/lib/workflow-engine/workflow-engine.module.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.mutations.d.ts +10 -0
- package/src/lib/workflow-engine/workflow-engine.mutations.js +112 -0
- package/src/lib/workflow-engine/workflow-engine.mutations.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.queries.d.ts +2 -0
- package/src/lib/workflow-engine/workflow-engine.queries.js +12 -0
- package/src/lib/workflow-engine/workflow-engine.queries.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.service.d.ts +44 -1
- package/src/lib/workflow-engine/workflow-engine.service.js +817 -41
- package/src/lib/workflow-engine/workflow-engine.service.js.map +1 -1
|
@@ -11,6 +11,7 @@ const form_definition_version_entity_1 = require("../form/form-definition-versio
|
|
|
11
11
|
const form_enums_1 = require("../form/form.enums");
|
|
12
12
|
const notification_service_1 = require("../notification/notification.service");
|
|
13
13
|
const notification_entity_1 = require("../notification/notification.entity");
|
|
14
|
+
const notification_enums_1 = require("../notification/notification.enums");
|
|
14
15
|
const signature_service_1 = require("../signature/signature.service");
|
|
15
16
|
const condition_service_1 = require("../condition/condition.service");
|
|
16
17
|
const manager_resolution_entity_1 = require("../organization/manager-resolution.entity");
|
|
@@ -21,6 +22,8 @@ const approval_template_version_entity_1 = require("../template/approval-templat
|
|
|
21
22
|
const approval_template_entity_1 = require("../template/approval-template.entity");
|
|
22
23
|
const template_enums_1 = require("../template/template.enums");
|
|
23
24
|
const activity_log_entity_1 = require("./activity-log.entity");
|
|
25
|
+
const adhoc_directive_entity_1 = require("./adhoc-directive.entity");
|
|
26
|
+
const adhoc_enums_1 = require("./adhoc.enums");
|
|
24
27
|
const approval_instance_entity_1 = require("./approval-instance.entity");
|
|
25
28
|
const task_decision_entity_1 = require("./task-decision.entity");
|
|
26
29
|
const task_candidate_entity_1 = require("./task-candidate.entity");
|
|
@@ -40,7 +43,7 @@ const WORKFLOW_READ_ALL_PERMISSIONS = new Set([
|
|
|
40
43
|
'workflow.read_all',
|
|
41
44
|
]);
|
|
42
45
|
let WorkflowEngineService = class WorkflowEngineService {
|
|
43
|
-
constructor(approvalInstanceRepository, workflowTokenRepository, taskRepository, taskCandidateRepository, taskDecisionRepository, notificationRepository, activityLogRepository, approvalTemplateRepository, approvalTemplateVersionRepository, formDefinitionVersionRepository, attachmentService, conditionService, delegationService, notificationService, signatureService, serviceTaskDispatcher = new workflow_service_task_dispatcher_token_1.DefaultWorkflowServiceTaskDispatcher()) {
|
|
46
|
+
constructor(approvalInstanceRepository, workflowTokenRepository, taskRepository, taskCandidateRepository, taskDecisionRepository, notificationRepository, activityLogRepository, adhocDirectiveRepository, approvalTemplateRepository, approvalTemplateVersionRepository, formDefinitionVersionRepository, attachmentService, conditionService, delegationService, notificationService, signatureService, serviceTaskDispatcher = new workflow_service_task_dispatcher_token_1.DefaultWorkflowServiceTaskDispatcher()) {
|
|
44
47
|
this.approvalInstanceRepository = approvalInstanceRepository;
|
|
45
48
|
this.workflowTokenRepository = workflowTokenRepository;
|
|
46
49
|
this.taskRepository = taskRepository;
|
|
@@ -48,6 +51,7 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
48
51
|
this.taskDecisionRepository = taskDecisionRepository;
|
|
49
52
|
this.notificationRepository = notificationRepository;
|
|
50
53
|
this.activityLogRepository = activityLogRepository;
|
|
54
|
+
this.adhocDirectiveRepository = adhocDirectiveRepository;
|
|
51
55
|
this.approvalTemplateRepository = approvalTemplateRepository;
|
|
52
56
|
this.approvalTemplateVersionRepository = approvalTemplateVersionRepository;
|
|
53
57
|
this.formDefinitionVersionRepository = formDefinitionVersionRepository;
|
|
@@ -186,19 +190,7 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
186
190
|
task.status !== workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS) {
|
|
187
191
|
throw new common_1.ConflictException(`Task ${task.id} is not pending`);
|
|
188
192
|
}
|
|
189
|
-
const taskCandidates = await
|
|
190
|
-
where: { taskId: task.id },
|
|
191
|
-
});
|
|
192
|
-
const actorCandidate = taskCandidates.find((candidate) => candidate.memberId === input.decidedByMemberId);
|
|
193
|
-
const isDirectAssignee = task.assigneeMemberId === input.decidedByMemberId;
|
|
194
|
-
if (!isDirectAssignee && !actorCandidate) {
|
|
195
|
-
throw new common_1.ConflictException(`Task ${task.id} is assigned to another member`);
|
|
196
|
-
}
|
|
197
|
-
if (actorCandidate &&
|
|
198
|
-
actorCandidate.status !== workflow_engine_enums_1.TaskCandidateStatusEnum.PENDING &&
|
|
199
|
-
actorCandidate.status !== workflow_engine_enums_1.TaskCandidateStatusEnum.CLAIMED) {
|
|
200
|
-
throw new common_1.ConflictException(`Task ${task.id} was already decided by this member`);
|
|
201
|
-
}
|
|
193
|
+
const { actorCandidate, taskCandidates } = await this.readTaskActorContext(manager, task, input.decidedByMemberId);
|
|
202
194
|
if (input.action !== workflow_engine_enums_1.TaskDecisionActionEnum.APPROVED &&
|
|
203
195
|
input.action !== workflow_engine_enums_1.TaskDecisionActionEnum.REJECTED &&
|
|
204
196
|
input.action !== workflow_engine_enums_1.TaskDecisionActionEnum.RETURNED &&
|
|
@@ -270,6 +262,13 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
270
262
|
}));
|
|
271
263
|
if (input.action === workflow_engine_enums_1.TaskDecisionActionEnum.TRANSFERRED) {
|
|
272
264
|
await this.transferTask(manager, instance, claimedTask, decision, signature, transferToMemberId, decisionComment, decidedAt, claimedCandidate);
|
|
265
|
+
await this.notificationService.resolveTaskNotifications({
|
|
266
|
+
actingMemberId: input.decidedByMemberId,
|
|
267
|
+
manager,
|
|
268
|
+
resolution: notification_enums_1.NotificationResolutionEnum.TRANSFERRED,
|
|
269
|
+
supersedeOthers: true,
|
|
270
|
+
taskId: claimedTask.id,
|
|
271
|
+
});
|
|
273
272
|
return decision;
|
|
274
273
|
}
|
|
275
274
|
const nextCandidateRows = await this.markTaskCandidateDecision({
|
|
@@ -309,8 +308,28 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
309
308
|
},
|
|
310
309
|
taskId: completedTask.id,
|
|
311
310
|
}));
|
|
311
|
+
// Resolve the recipient's "待簽" notification so its inline 同意/拒絕
|
|
312
|
+
// actions disappear once the task is decided. When the task has ended
|
|
313
|
+
// (rejected / returned, or an approval that completed it), every other
|
|
314
|
+
// candidate's notification is superseded too; a non-completing approval
|
|
315
|
+
// on a multi-approver task leaves the remaining approvers' open.
|
|
316
|
+
const taskEnded = input.action !== workflow_engine_enums_1.TaskDecisionActionEnum.APPROVED || shouldCompleteTask;
|
|
317
|
+
await this.notificationService.resolveTaskNotifications({
|
|
318
|
+
actingMemberId: input.decidedByMemberId,
|
|
319
|
+
manager,
|
|
320
|
+
resolution: mapDecisionToResolution(input.action),
|
|
321
|
+
supersedeOthers: taskEnded,
|
|
322
|
+
taskId: completedTask.id,
|
|
323
|
+
});
|
|
312
324
|
if (input.action === workflow_engine_enums_1.TaskDecisionActionEnum.APPROVED &&
|
|
313
325
|
shouldCompleteTask) {
|
|
326
|
+
// Ad-hoc gate: the token may only advance once every task bound to
|
|
327
|
+
// this token+node (the original task plus any ad-hoc countersign /
|
|
328
|
+
// pre-approval tasks) has reached a terminal state. Nodes without
|
|
329
|
+
// ad-hoc tasks keep the original single-task behaviour.
|
|
330
|
+
if (await this.hasOpenTasksForTokenNode(manager, claimedTask.tokenId, claimedTask.nodeId)) {
|
|
331
|
+
return decision;
|
|
332
|
+
}
|
|
314
333
|
const token = await tokenRepository.findOne({
|
|
315
334
|
where: { id: claimedTask.tokenId },
|
|
316
335
|
});
|
|
@@ -325,6 +344,7 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
325
344
|
consumedAt: null,
|
|
326
345
|
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.ACTIVE,
|
|
327
346
|
});
|
|
347
|
+
await this.dispatchAdhocStageNotifications(manager, instance, claimedTask.nodeId, 'APPROVED');
|
|
328
348
|
await this.advanceTokenToOutgoingNodes(manager, instance, activeToken, readWorkflowNodeOrThrow(instance.workflowSnapshot, claimedTask.nodeId));
|
|
329
349
|
await this.processRunningInstance(manager, instance);
|
|
330
350
|
return decision;
|
|
@@ -336,6 +356,9 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
336
356
|
await this.returnInstanceToNode(manager, instance, completedTask, returnToNodeId, decidedAt);
|
|
337
357
|
return decision;
|
|
338
358
|
}
|
|
359
|
+
if (await this.handleAdhocPreApprovalRejection(manager, instance, completedTask, decidedAt, decisionComment)) {
|
|
360
|
+
return decision;
|
|
361
|
+
}
|
|
339
362
|
await this.rejectInstance(manager, instance, completedTask, decidedAt);
|
|
340
363
|
return decision;
|
|
341
364
|
});
|
|
@@ -361,11 +384,17 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
361
384
|
}
|
|
362
385
|
const cancelledAt = new Date();
|
|
363
386
|
await this.consumeOpenRuntimeState(manager, instance, cancelledAt, workflow_engine_enums_1.TaskStatusEnum.CANCELLED);
|
|
387
|
+
await this.notificationService.supersedeInstanceTaskNotifications({
|
|
388
|
+
instanceId: instance.id,
|
|
389
|
+
manager,
|
|
390
|
+
});
|
|
364
391
|
const cancelledInstance = await instanceRepository.save({
|
|
365
392
|
...instance,
|
|
366
393
|
completedAt: cancelledAt,
|
|
367
394
|
state: workflow_engine_enums_1.ApprovalInstanceStateEnum.CANCELLED,
|
|
368
395
|
});
|
|
396
|
+
await this.dispatchAdhocCompletionNotifications(manager, cancelledInstance, workflow_engine_enums_1.ApprovalInstanceStateEnum.CANCELLED);
|
|
397
|
+
await this.cancelPendingAdhocDirectives(manager, instance.id, null);
|
|
369
398
|
await manager.getRepository(activity_log_entity_1.ActivityLogEntity).save(manager.getRepository(activity_log_entity_1.ActivityLogEntity).create({
|
|
370
399
|
actorMemberId: input.cancelledByMemberId,
|
|
371
400
|
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.INSTANCE_CANCELLED,
|
|
@@ -428,6 +457,567 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
428
457
|
return runtimeInstance;
|
|
429
458
|
});
|
|
430
459
|
}
|
|
460
|
+
async requestAdhocCountersign({ comment, requestedByMemberId, target, taskId, }) {
|
|
461
|
+
return this.approvalInstanceRepository.manager.transaction(async (manager) => {
|
|
462
|
+
const { instance, node, task } = await this.loadAdhocOperationContext(manager, taskId, requestedByMemberId, { requireAllowAddSigner: true });
|
|
463
|
+
const targetValue = buildAdhocTargetValue(target);
|
|
464
|
+
// Rejects WEBHOOK targets — countersigners must be members.
|
|
465
|
+
buildAdhocApproverResolver(targetValue);
|
|
466
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
467
|
+
const directive = await directiveRepository.save(directiveRepository.create({
|
|
468
|
+
channels: null,
|
|
469
|
+
comment: comment?.trim() || null,
|
|
470
|
+
consumedAt: null,
|
|
471
|
+
createdByMemberId: requestedByMemberId,
|
|
472
|
+
instanceId: instance.id,
|
|
473
|
+
onReject: null,
|
|
474
|
+
originNodeId: task.nodeId,
|
|
475
|
+
originTaskId: task.id,
|
|
476
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.PENDING,
|
|
477
|
+
targetKind: targetValue.kind,
|
|
478
|
+
targetValue: { ...targetValue },
|
|
479
|
+
type: adhoc_enums_1.AdhocDirectiveTypeEnum.COUNTERSIGN,
|
|
480
|
+
}));
|
|
481
|
+
await this.recordAdhocDirectiveActivity(manager, directive, workflow_engine_enums_1.ActivityLogEventTypeEnum.ADHOC_DIRECTIVE_CREATED, requestedByMemberId, { nodeLabel: node.data.label });
|
|
482
|
+
return directive;
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
async requestAdhocPreApproval({ comment, onReject, requestedByMemberId, target, taskId, }) {
|
|
486
|
+
return this.approvalInstanceRepository.manager.transaction(async (manager) => {
|
|
487
|
+
const { instance, node, task } = await this.loadAdhocOperationContext(manager, taskId, requestedByMemberId, { requireAllowAddSigner: true });
|
|
488
|
+
const targetValue = buildAdhocTargetValue(target);
|
|
489
|
+
// Rejects WEBHOOK targets — pre-approvers must be members.
|
|
490
|
+
buildAdhocApproverResolver(targetValue);
|
|
491
|
+
const now = new Date();
|
|
492
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
493
|
+
const directive = await directiveRepository.save(directiveRepository.create({
|
|
494
|
+
channels: null,
|
|
495
|
+
comment: comment?.trim() || null,
|
|
496
|
+
consumedAt: now,
|
|
497
|
+
createdByMemberId: requestedByMemberId,
|
|
498
|
+
instanceId: instance.id,
|
|
499
|
+
onReject,
|
|
500
|
+
originNodeId: task.nodeId,
|
|
501
|
+
originTaskId: task.id,
|
|
502
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.CONSUMED,
|
|
503
|
+
targetKind: targetValue.kind,
|
|
504
|
+
targetValue: { ...targetValue },
|
|
505
|
+
type: adhoc_enums_1.AdhocDirectiveTypeEnum.PRE_APPROVAL,
|
|
506
|
+
}));
|
|
507
|
+
const adhocTask = await this.createAdhocTaskForDirective(manager, instance, task.tokenId, node, directive, now);
|
|
508
|
+
await this.recordAdhocDirectiveActivity(manager, directive, workflow_engine_enums_1.ActivityLogEventTypeEnum.ADHOC_DIRECTIVE_CREATED, requestedByMemberId, { adhocTaskId: adhocTask.id, nodeLabel: node.data.label, onReject });
|
|
509
|
+
return adhocTask;
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
async configureAdhocNotification({ channels, requestedByMemberId, target, taskId, type, }) {
|
|
513
|
+
return this.approvalInstanceRepository.manager.transaction(async (manager) => {
|
|
514
|
+
const { instance, node, task } = await this.loadAdhocOperationContext(manager, taskId, requestedByMemberId, { requireAllowAddSigner: false });
|
|
515
|
+
const targetValue = buildAdhocTargetValue(target);
|
|
516
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
517
|
+
const directive = await directiveRepository.save(directiveRepository.create({
|
|
518
|
+
channels: channels?.length ? [...channels] : null,
|
|
519
|
+
comment: null,
|
|
520
|
+
consumedAt: null,
|
|
521
|
+
createdByMemberId: requestedByMemberId,
|
|
522
|
+
instanceId: instance.id,
|
|
523
|
+
onReject: null,
|
|
524
|
+
originNodeId: task.nodeId,
|
|
525
|
+
originTaskId: task.id,
|
|
526
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.PENDING,
|
|
527
|
+
targetKind: targetValue.kind,
|
|
528
|
+
targetValue: { ...targetValue },
|
|
529
|
+
type,
|
|
530
|
+
}));
|
|
531
|
+
await this.recordAdhocDirectiveActivity(manager, directive, workflow_engine_enums_1.ActivityLogEventTypeEnum.ADHOC_DIRECTIVE_CREATED, requestedByMemberId, { nodeLabel: node.data.label });
|
|
532
|
+
return directive;
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
async cancelAdhocDirective({ cancelledByMemberId, directiveId, }) {
|
|
536
|
+
return this.approvalInstanceRepository.manager.transaction(async (manager) => {
|
|
537
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
538
|
+
const directive = await directiveRepository.findOne({
|
|
539
|
+
where: { id: directiveId },
|
|
540
|
+
});
|
|
541
|
+
if (!directive) {
|
|
542
|
+
throw new common_1.NotFoundException(`Ad-hoc directive ${directiveId} was not found`);
|
|
543
|
+
}
|
|
544
|
+
await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
|
|
545
|
+
directive.instanceId,
|
|
546
|
+
]);
|
|
547
|
+
if (directive.createdByMemberId !== cancelledByMemberId) {
|
|
548
|
+
throw new common_1.ConflictException(`Ad-hoc directive ${directive.id} can only be cancelled by its creator`);
|
|
549
|
+
}
|
|
550
|
+
if (directive.status !== adhoc_enums_1.AdhocDirectiveStatusEnum.PENDING) {
|
|
551
|
+
throw new common_1.ConflictException(`Ad-hoc directive ${directive.id} is not pending`);
|
|
552
|
+
}
|
|
553
|
+
const cancelledDirective = await directiveRepository.save({
|
|
554
|
+
...directive,
|
|
555
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.CANCELLED,
|
|
556
|
+
});
|
|
557
|
+
await this.recordAdhocDirectiveActivity(manager, cancelledDirective, workflow_engine_enums_1.ActivityLogEventTypeEnum.ADHOC_DIRECTIVE_CANCELLED, cancelledByMemberId);
|
|
558
|
+
return cancelledDirective;
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
async listAdhocDirectives(instanceId, scope) {
|
|
562
|
+
await this.getApprovalInstance(instanceId, scope);
|
|
563
|
+
return this.adhocDirectiveRepository.find({
|
|
564
|
+
order: { createdAt: 'ASC' },
|
|
565
|
+
where: { instanceId },
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
async readTaskActorContext(manager, task, actingMemberId) {
|
|
569
|
+
const taskCandidates = await manager
|
|
570
|
+
.getRepository(task_candidate_entity_1.TaskCandidateEntity)
|
|
571
|
+
.find({ where: { taskId: task.id } });
|
|
572
|
+
const actorCandidate = taskCandidates.find((candidate) => candidate.memberId === actingMemberId) ?? null;
|
|
573
|
+
const isDirectAssignee = task.assigneeMemberId === actingMemberId;
|
|
574
|
+
if (!isDirectAssignee && !actorCandidate) {
|
|
575
|
+
throw new common_1.ConflictException(`Task ${task.id} is assigned to another member`);
|
|
576
|
+
}
|
|
577
|
+
if (actorCandidate &&
|
|
578
|
+
actorCandidate.status !== workflow_engine_enums_1.TaskCandidateStatusEnum.PENDING &&
|
|
579
|
+
actorCandidate.status !== workflow_engine_enums_1.TaskCandidateStatusEnum.CLAIMED) {
|
|
580
|
+
throw new common_1.ConflictException(`Task ${task.id} was already decided by this member`);
|
|
581
|
+
}
|
|
582
|
+
return { actorCandidate, taskCandidates };
|
|
583
|
+
}
|
|
584
|
+
async loadAdhocOperationContext(manager, taskId, actingMemberId, options) {
|
|
585
|
+
const task = await manager
|
|
586
|
+
.getRepository(task_entity_1.TaskEntity)
|
|
587
|
+
.findOne({ where: { id: taskId } });
|
|
588
|
+
if (!task) {
|
|
589
|
+
throw new common_1.NotFoundException(`Task ${taskId} was not found`);
|
|
590
|
+
}
|
|
591
|
+
await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
|
|
592
|
+
task.instanceId,
|
|
593
|
+
]);
|
|
594
|
+
if (task.status !== workflow_engine_enums_1.TaskStatusEnum.PENDING &&
|
|
595
|
+
task.status !== workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS) {
|
|
596
|
+
throw new common_1.ConflictException(`Task ${task.id} is not pending`);
|
|
597
|
+
}
|
|
598
|
+
await this.readTaskActorContext(manager, task, actingMemberId);
|
|
599
|
+
const instance = await manager
|
|
600
|
+
.getRepository(approval_instance_entity_1.ApprovalInstanceEntity)
|
|
601
|
+
.findOne({ where: { id: task.instanceId } });
|
|
602
|
+
if (!instance) {
|
|
603
|
+
throw new common_1.NotFoundException(`Approval instance ${task.instanceId} was not found`);
|
|
604
|
+
}
|
|
605
|
+
if (instance.state !== workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING) {
|
|
606
|
+
throw new common_1.ConflictException(`Approval instance ${instance.id} is not running`);
|
|
607
|
+
}
|
|
608
|
+
const node = readWorkflowNodeOrThrow(instance.workflowSnapshot, task.nodeId);
|
|
609
|
+
if (node.type !== 'userTask') {
|
|
610
|
+
throw new common_1.ConflictException(`Task ${task.id} is not bound to a user task node`);
|
|
611
|
+
}
|
|
612
|
+
if (options.requireAllowAddSigner && !node.data.allowAddSigner) {
|
|
613
|
+
throw new common_1.ForbiddenException(`簽核節點「${node.data.label}」does not allow ad-hoc signers`);
|
|
614
|
+
}
|
|
615
|
+
return { instance, node, task };
|
|
616
|
+
}
|
|
617
|
+
async recordAdhocDirectiveActivity(manager, directive, eventType, actorMemberId, extraPayload = {}) {
|
|
618
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
619
|
+
await activityRepository.save(activityRepository.create({
|
|
620
|
+
actorMemberId,
|
|
621
|
+
eventType,
|
|
622
|
+
instanceId: directive.instanceId,
|
|
623
|
+
nodeId: directive.originNodeId,
|
|
624
|
+
payload: {
|
|
625
|
+
...extraPayload,
|
|
626
|
+
directiveId: directive.id,
|
|
627
|
+
directiveStatus: directive.status,
|
|
628
|
+
directiveType: directive.type,
|
|
629
|
+
targetKind: directive.targetKind,
|
|
630
|
+
},
|
|
631
|
+
taskId: directive.originTaskId,
|
|
632
|
+
}));
|
|
633
|
+
}
|
|
634
|
+
async createAdhocTaskForDirective(manager, instance, tokenId, node, directive, now) {
|
|
635
|
+
const taskRepository = manager.getRepository(task_entity_1.TaskEntity);
|
|
636
|
+
const taskCandidateRepository = manager.getRepository(task_candidate_entity_1.TaskCandidateEntity);
|
|
637
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
638
|
+
const label = directive.type === adhoc_enums_1.AdhocDirectiveTypeEnum.COUNTERSIGN
|
|
639
|
+
? '臨時會簽'
|
|
640
|
+
: '臨時加簽';
|
|
641
|
+
const resolvedCandidates = await this.resolveApproverResolver(manager, instance, buildAdhocApproverResolver(readAdhocTargetValue(directive)), `${label}「${node.data.label}」`);
|
|
642
|
+
const candidates = await this.applyDelegationToResolvedCandidates(instance, node.id, resolvedCandidates);
|
|
643
|
+
if (candidates.length === 0) {
|
|
644
|
+
throw new common_1.ConflictException(`${label}「${node.data.label}」 did not resolve to any member id`);
|
|
645
|
+
}
|
|
646
|
+
const primaryCandidate = candidates[0];
|
|
647
|
+
const task = await taskRepository.save(taskRepository.create({
|
|
648
|
+
adhocDirectiveId: directive.id,
|
|
649
|
+
adhocOriginTaskId: directive.originTaskId,
|
|
650
|
+
adhocType: directive.type,
|
|
651
|
+
assigneeMemberId: candidates.length === 1 ? primaryCandidate.memberId : null,
|
|
652
|
+
assignmentType: candidates.length === 1
|
|
653
|
+
? workflow_engine_enums_1.TaskAssignmentTypeEnum.DIRECT_MEMBER
|
|
654
|
+
: workflow_engine_enums_1.TaskAssignmentTypeEnum.CANDIDATE_GROUP,
|
|
655
|
+
completedAt: null,
|
|
656
|
+
createdAt: now,
|
|
657
|
+
decisionPolicySnapshot: { type: 'SINGLE' },
|
|
658
|
+
delegationChain: candidates.length === 1 ? primaryCandidate.delegationChain : [],
|
|
659
|
+
instanceId: instance.id,
|
|
660
|
+
isAdhoc: true,
|
|
661
|
+
nodeId: node.id,
|
|
662
|
+
openedAt: null,
|
|
663
|
+
originalAssigneeMemberId: candidates.length === 1 ? primaryCandidate.originalMemberId : null,
|
|
664
|
+
slaDueAt: null,
|
|
665
|
+
status: workflow_engine_enums_1.TaskStatusEnum.PENDING,
|
|
666
|
+
tokenId,
|
|
667
|
+
}));
|
|
668
|
+
const savedCandidates = await taskCandidateRepository.save(candidates.map((candidate) => taskCandidateRepository.create({
|
|
669
|
+
claimedAt: null,
|
|
670
|
+
createdAt: now,
|
|
671
|
+
decidedAt: null,
|
|
672
|
+
delegationChain: candidate.delegationChain,
|
|
673
|
+
memberId: candidate.memberId,
|
|
674
|
+
originalMemberId: candidate.originalMemberId,
|
|
675
|
+
sourceType: candidate.sourceType,
|
|
676
|
+
status: workflow_engine_enums_1.TaskCandidateStatusEnum.PENDING,
|
|
677
|
+
taskId: task.id,
|
|
678
|
+
})));
|
|
679
|
+
task.candidateMemberIds = savedCandidates.map((candidate) => candidate.memberId);
|
|
680
|
+
await activityRepository.save(activityRepository.create({
|
|
681
|
+
actorMemberId: directive.createdByMemberId,
|
|
682
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TASK_CREATED,
|
|
683
|
+
instanceId: instance.id,
|
|
684
|
+
nodeId: node.id,
|
|
685
|
+
payload: {
|
|
686
|
+
adhocDirectiveId: directive.id,
|
|
687
|
+
adhocType: directive.type,
|
|
688
|
+
assigneeMemberId: task.assigneeMemberId,
|
|
689
|
+
assignmentType: task.assignmentType,
|
|
690
|
+
candidateMemberIds: savedCandidates.map((candidate) => candidate.memberId),
|
|
691
|
+
originTaskId: directive.originTaskId,
|
|
692
|
+
tokenId,
|
|
693
|
+
},
|
|
694
|
+
taskId: task.id,
|
|
695
|
+
}));
|
|
696
|
+
await savedCandidates.reduce(async (previous, candidate) => {
|
|
697
|
+
await previous;
|
|
698
|
+
await this.notificationService.createTaskAssignedNotification({
|
|
699
|
+
instance,
|
|
700
|
+
manager,
|
|
701
|
+
node,
|
|
702
|
+
task: Object.assign(new task_entity_1.TaskEntity(), task, {
|
|
703
|
+
assigneeMemberId: candidate.memberId,
|
|
704
|
+
delegationChain: candidate.delegationChain,
|
|
705
|
+
originalAssigneeMemberId: candidate.originalMemberId,
|
|
706
|
+
}),
|
|
707
|
+
});
|
|
708
|
+
}, Promise.resolve());
|
|
709
|
+
return task;
|
|
710
|
+
}
|
|
711
|
+
async spawnCountersignTasksForNode(manager, instance, token, node, now) {
|
|
712
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
713
|
+
const directives = await directiveRepository.find({
|
|
714
|
+
order: { createdAt: 'ASC' },
|
|
715
|
+
where: {
|
|
716
|
+
instanceId: instance.id,
|
|
717
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.PENDING,
|
|
718
|
+
type: adhoc_enums_1.AdhocDirectiveTypeEnum.COUNTERSIGN,
|
|
719
|
+
},
|
|
720
|
+
});
|
|
721
|
+
if (directives.length === 0) {
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
await directives.reduce(async (previous, directive) => {
|
|
725
|
+
await previous;
|
|
726
|
+
try {
|
|
727
|
+
await this.createAdhocTaskForDirective(manager, instance, token.id, node, directive, now);
|
|
728
|
+
await directiveRepository.save({
|
|
729
|
+
...directive,
|
|
730
|
+
consumedAt: now,
|
|
731
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.CONSUMED,
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
catch (error) {
|
|
735
|
+
// A countersign target that no longer resolves must not block the
|
|
736
|
+
// main flow — cancel the directive and record the failure.
|
|
737
|
+
await directiveRepository.save({
|
|
738
|
+
...directive,
|
|
739
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.CANCELLED,
|
|
740
|
+
});
|
|
741
|
+
await this.recordAdhocDirectiveActivity(manager, directive, workflow_engine_enums_1.ActivityLogEventTypeEnum.ADHOC_DIRECTIVE_CANCELLED, null, {
|
|
742
|
+
error: error instanceof Error
|
|
743
|
+
? error.message
|
|
744
|
+
: 'Countersign target resolution failed',
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
}, Promise.resolve());
|
|
748
|
+
}
|
|
749
|
+
async hasOpenTasksForTokenNode(manager, tokenId, nodeId) {
|
|
750
|
+
const tasks = await manager.getRepository(task_entity_1.TaskEntity).find({
|
|
751
|
+
where: { nodeId, tokenId },
|
|
752
|
+
});
|
|
753
|
+
return tasks.some((task) => task.tokenId === tokenId &&
|
|
754
|
+
task.nodeId === nodeId &&
|
|
755
|
+
(task.status === workflow_engine_enums_1.TaskStatusEnum.PENDING ||
|
|
756
|
+
task.status === workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS));
|
|
757
|
+
}
|
|
758
|
+
async handleAdhocPreApprovalRejection(manager, instance, task, decidedAt, decisionComment) {
|
|
759
|
+
if (!task.isAdhoc ||
|
|
760
|
+
task.adhocType !== adhoc_enums_1.AdhocDirectiveTypeEnum.PRE_APPROVAL ||
|
|
761
|
+
!task.adhocDirectiveId) {
|
|
762
|
+
return false;
|
|
763
|
+
}
|
|
764
|
+
const directive = await manager
|
|
765
|
+
.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity)
|
|
766
|
+
.findOne({ where: { id: task.adhocDirectiveId } });
|
|
767
|
+
if (directive?.onReject !==
|
|
768
|
+
adhoc_enums_1.AdhocPreApprovalRejectBehaviorEnum.RETURN_TO_ORIGIN) {
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
const taskRepository = manager.getRepository(task_entity_1.TaskEntity);
|
|
772
|
+
const taskCandidateRepository = manager.getRepository(task_candidate_entity_1.TaskCandidateEntity);
|
|
773
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
774
|
+
const originTask = task.adhocOriginTaskId
|
|
775
|
+
? await taskRepository.findOne({ where: { id: task.adhocOriginTaskId } })
|
|
776
|
+
: null;
|
|
777
|
+
const originTaskIsOpen = originTask?.status === workflow_engine_enums_1.TaskStatusEnum.PENDING ||
|
|
778
|
+
originTask?.status === workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS;
|
|
779
|
+
// Safety: when there is nobody to hand the decision back to (origin task
|
|
780
|
+
// missing, or already closed without a resolvable assignee), fall back to
|
|
781
|
+
// the default rejection path instead of leaving the token stuck WAITING
|
|
782
|
+
// with no open task.
|
|
783
|
+
if (!originTask || (!originTaskIsOpen && !originTask.assigneeMemberId)) {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
const node = readWorkflowNodeOrThrow(instance.workflowSnapshot, task.nodeId);
|
|
787
|
+
if (!originTaskIsOpen && originTask.assigneeMemberId) {
|
|
788
|
+
// The origin approver already decided — reopen a fresh decision task so
|
|
789
|
+
// the stage can be re-decided after the rejected pre-approval.
|
|
790
|
+
const reopenedTask = await taskRepository.save(taskRepository.create({
|
|
791
|
+
assigneeMemberId: originTask.assigneeMemberId,
|
|
792
|
+
assignmentType: workflow_engine_enums_1.TaskAssignmentTypeEnum.DIRECT_MEMBER,
|
|
793
|
+
completedAt: null,
|
|
794
|
+
createdAt: decidedAt,
|
|
795
|
+
decisionPolicySnapshot: { type: 'SINGLE' },
|
|
796
|
+
delegationChain: originTask.delegationChain,
|
|
797
|
+
instanceId: instance.id,
|
|
798
|
+
nodeId: originTask.nodeId,
|
|
799
|
+
openedAt: null,
|
|
800
|
+
originalAssigneeMemberId: originTask.originalAssigneeMemberId,
|
|
801
|
+
slaDueAt: null,
|
|
802
|
+
status: workflow_engine_enums_1.TaskStatusEnum.PENDING,
|
|
803
|
+
tokenId: originTask.tokenId,
|
|
804
|
+
}));
|
|
805
|
+
await taskCandidateRepository.save(taskCandidateRepository.create({
|
|
806
|
+
claimedAt: null,
|
|
807
|
+
createdAt: decidedAt,
|
|
808
|
+
decidedAt: null,
|
|
809
|
+
delegationChain: originTask.delegationChain,
|
|
810
|
+
memberId: originTask.assigneeMemberId,
|
|
811
|
+
originalMemberId: originTask.originalAssigneeMemberId ?? originTask.assigneeMemberId,
|
|
812
|
+
sourceType: 'DIRECT',
|
|
813
|
+
status: workflow_engine_enums_1.TaskCandidateStatusEnum.PENDING,
|
|
814
|
+
taskId: reopenedTask.id,
|
|
815
|
+
}));
|
|
816
|
+
await activityRepository.save(activityRepository.create({
|
|
817
|
+
actorMemberId: task.assigneeMemberId,
|
|
818
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TASK_CREATED,
|
|
819
|
+
instanceId: instance.id,
|
|
820
|
+
nodeId: originTask.nodeId,
|
|
821
|
+
payload: {
|
|
822
|
+
adhocDirectiveId: directive.id,
|
|
823
|
+
assigneeMemberId: originTask.assigneeMemberId,
|
|
824
|
+
reopenedFromTaskId: originTask.id,
|
|
825
|
+
rejectedAdhocTaskId: task.id,
|
|
826
|
+
tokenId: originTask.tokenId,
|
|
827
|
+
},
|
|
828
|
+
taskId: reopenedTask.id,
|
|
829
|
+
}));
|
|
830
|
+
if (node.type === 'userTask') {
|
|
831
|
+
await this.notificationService.createTaskAssignedNotification({
|
|
832
|
+
instance,
|
|
833
|
+
manager,
|
|
834
|
+
node,
|
|
835
|
+
task: reopenedTask,
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
const recipientMemberIds = await this.readOpenTaskRecipientMemberIds(manager, originTask);
|
|
840
|
+
if (recipientMemberIds.length > 0) {
|
|
841
|
+
await this.notificationService.createAdhocWorkflowNotifications({
|
|
842
|
+
channels: null,
|
|
843
|
+
instance,
|
|
844
|
+
manager,
|
|
845
|
+
message: `案件「${instance.title}」的臨時加簽已被拒絕${decisionComment ? `:${decisionComment}` : '。'}`,
|
|
846
|
+
payload: {
|
|
847
|
+
adhocTaskId: task.id,
|
|
848
|
+
directiveId: directive.id,
|
|
849
|
+
nodeId: task.nodeId,
|
|
850
|
+
originTaskId: originTask.id,
|
|
851
|
+
type: 'PRE_APPROVAL_RETURNED',
|
|
852
|
+
},
|
|
853
|
+
recipientMemberIds,
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
await activityRepository.save(activityRepository.create({
|
|
857
|
+
actorMemberId: task.assigneeMemberId,
|
|
858
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.ADHOC_PRE_APPROVAL_RETURNED,
|
|
859
|
+
instanceId: instance.id,
|
|
860
|
+
nodeId: task.nodeId,
|
|
861
|
+
payload: {
|
|
862
|
+
comment: decisionComment,
|
|
863
|
+
directiveId: directive.id,
|
|
864
|
+
originTaskId: originTask.id,
|
|
865
|
+
rejectedAdhocTaskId: task.id,
|
|
866
|
+
},
|
|
867
|
+
taskId: task.id,
|
|
868
|
+
}));
|
|
869
|
+
return true;
|
|
870
|
+
}
|
|
871
|
+
async readOpenTaskRecipientMemberIds(manager, task) {
|
|
872
|
+
if (task.assigneeMemberId) {
|
|
873
|
+
return [task.assigneeMemberId];
|
|
874
|
+
}
|
|
875
|
+
const candidates = await manager
|
|
876
|
+
.getRepository(task_candidate_entity_1.TaskCandidateEntity)
|
|
877
|
+
.find({ where: { taskId: task.id } });
|
|
878
|
+
return uniqueTexts(candidates
|
|
879
|
+
.filter((candidate) => candidate.status === workflow_engine_enums_1.TaskCandidateStatusEnum.PENDING ||
|
|
880
|
+
candidate.status === workflow_engine_enums_1.TaskCandidateStatusEnum.CLAIMED)
|
|
881
|
+
.map((candidate) => candidate.memberId));
|
|
882
|
+
}
|
|
883
|
+
async dispatchAdhocStageNotifications(manager, instance, nodeId, outcome) {
|
|
884
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
885
|
+
const directives = await directiveRepository.find({
|
|
886
|
+
order: { createdAt: 'ASC' },
|
|
887
|
+
where: {
|
|
888
|
+
instanceId: instance.id,
|
|
889
|
+
originNodeId: nodeId,
|
|
890
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.PENDING,
|
|
891
|
+
type: adhoc_enums_1.AdhocDirectiveTypeEnum.STAGE_NOTIFY,
|
|
892
|
+
},
|
|
893
|
+
});
|
|
894
|
+
if (directives.length === 0) {
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
const node = readWorkflowNodeOrThrow(instance.workflowSnapshot, nodeId);
|
|
898
|
+
const nodeLabel = node.data.label;
|
|
899
|
+
const outcomeLabel = readAdhocStageOutcomeLabel(outcome);
|
|
900
|
+
const consumedAt = new Date();
|
|
901
|
+
await directives.reduce(async (previous, directive) => {
|
|
902
|
+
await previous;
|
|
903
|
+
await this.dispatchAdhocDirectiveNotification(manager, instance, directive, `案件「${instance.title}」的階段「${nodeLabel}」已${outcomeLabel}。`, {
|
|
904
|
+
nodeId,
|
|
905
|
+
nodeLabel,
|
|
906
|
+
outcome,
|
|
907
|
+
type: adhoc_enums_1.AdhocDirectiveTypeEnum.STAGE_NOTIFY,
|
|
908
|
+
});
|
|
909
|
+
await directiveRepository.save({
|
|
910
|
+
...directive,
|
|
911
|
+
consumedAt,
|
|
912
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.CONSUMED,
|
|
913
|
+
});
|
|
914
|
+
}, Promise.resolve());
|
|
915
|
+
}
|
|
916
|
+
async dispatchAdhocCompletionNotifications(manager, instance, finalState) {
|
|
917
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
918
|
+
const directives = await directiveRepository.find({
|
|
919
|
+
order: { createdAt: 'ASC' },
|
|
920
|
+
where: {
|
|
921
|
+
instanceId: instance.id,
|
|
922
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.PENDING,
|
|
923
|
+
type: adhoc_enums_1.AdhocDirectiveTypeEnum.COMPLETION_NOTIFY,
|
|
924
|
+
},
|
|
925
|
+
});
|
|
926
|
+
if (directives.length === 0) {
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
const finalStateLabel = readInstanceFinalStateLabel(finalState);
|
|
930
|
+
const consumedAt = new Date();
|
|
931
|
+
await directives.reduce(async (previous, directive) => {
|
|
932
|
+
await previous;
|
|
933
|
+
await this.dispatchAdhocDirectiveNotification(manager, instance, directive, `案件「${instance.title}」已結案(${finalStateLabel})。`, {
|
|
934
|
+
finalState,
|
|
935
|
+
type: adhoc_enums_1.AdhocDirectiveTypeEnum.COMPLETION_NOTIFY,
|
|
936
|
+
});
|
|
937
|
+
await directiveRepository.save({
|
|
938
|
+
...directive,
|
|
939
|
+
consumedAt,
|
|
940
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.CONSUMED,
|
|
941
|
+
});
|
|
942
|
+
}, Promise.resolve());
|
|
943
|
+
}
|
|
944
|
+
async dispatchAdhocDirectiveNotification(manager, instance, directive, message, payload) {
|
|
945
|
+
const targetValue = readAdhocTargetValue(directive);
|
|
946
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
947
|
+
if (targetValue.kind === adhoc_enums_1.AdhocTargetKindEnum.WEBHOOK) {
|
|
948
|
+
const result = await executeAdhocWebhookDispatch(this.serviceTaskDispatcher, targetValue, {
|
|
949
|
+
...payload,
|
|
950
|
+
instanceId: instance.id,
|
|
951
|
+
instanceTitle: instance.title,
|
|
952
|
+
message,
|
|
953
|
+
triggeredAt: new Date().toISOString(),
|
|
954
|
+
});
|
|
955
|
+
await activityRepository.save(activityRepository.create({
|
|
956
|
+
actorMemberId: null,
|
|
957
|
+
eventType: result.ok
|
|
958
|
+
? workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_EXECUTED
|
|
959
|
+
: workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
|
|
960
|
+
instanceId: instance.id,
|
|
961
|
+
nodeId: directive.originNodeId,
|
|
962
|
+
payload: {
|
|
963
|
+
action: 'ADHOC_WEBHOOK',
|
|
964
|
+
directiveId: directive.id,
|
|
965
|
+
error: result.error,
|
|
966
|
+
ok: result.ok,
|
|
967
|
+
status: result.status,
|
|
968
|
+
url: targetValue.webhookUrl,
|
|
969
|
+
},
|
|
970
|
+
taskId: null,
|
|
971
|
+
}));
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
try {
|
|
975
|
+
const recipients = await this.resolveApproverResolver(manager, instance, buildAdhocApproverResolver(targetValue), '臨時通知對象');
|
|
976
|
+
await this.notificationService.createAdhocWorkflowNotifications({
|
|
977
|
+
channels: directive.channels,
|
|
978
|
+
instance,
|
|
979
|
+
manager,
|
|
980
|
+
message,
|
|
981
|
+
payload: { ...payload, directiveId: directive.id },
|
|
982
|
+
recipientMemberIds: recipients.map((recipient) => recipient.memberId),
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
catch (error) {
|
|
986
|
+
// Notification target resolution must never block the workflow
|
|
987
|
+
// transition that triggered it — record the failure instead.
|
|
988
|
+
await activityRepository.save(activityRepository.create({
|
|
989
|
+
actorMemberId: null,
|
|
990
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
|
|
991
|
+
instanceId: instance.id,
|
|
992
|
+
nodeId: directive.originNodeId,
|
|
993
|
+
payload: {
|
|
994
|
+
action: 'ADHOC_NOTIFY',
|
|
995
|
+
directiveId: directive.id,
|
|
996
|
+
error: error instanceof Error
|
|
997
|
+
? error.message
|
|
998
|
+
: 'Ad-hoc notification dispatch failed',
|
|
999
|
+
},
|
|
1000
|
+
taskId: null,
|
|
1001
|
+
}));
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
async cancelPendingAdhocDirectives(manager, instanceId, types) {
|
|
1005
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
1006
|
+
const directives = await directiveRepository.find({
|
|
1007
|
+
where: {
|
|
1008
|
+
instanceId,
|
|
1009
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.PENDING,
|
|
1010
|
+
...(types ? { type: (0, typeorm_2.In)([...types]) } : {}),
|
|
1011
|
+
},
|
|
1012
|
+
});
|
|
1013
|
+
if (directives.length === 0) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
await directiveRepository.save(directives.map((directive) => ({
|
|
1017
|
+
...directive,
|
|
1018
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.CANCELLED,
|
|
1019
|
+
})));
|
|
1020
|
+
}
|
|
431
1021
|
dryRunApprovalWorkflow(input) {
|
|
432
1022
|
const workflowDefinition = parseWorkflowDefinition(input.workflowDefinitionJson);
|
|
433
1023
|
const formData = parseJsonObject(input.formDataJson, 'formDataJson');
|
|
@@ -941,7 +1531,16 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
941
1531
|
const decisions = await this.taskDecisionRepository.find({
|
|
942
1532
|
where: { taskId: (0, typeorm_2.In)(taskIds) },
|
|
943
1533
|
});
|
|
944
|
-
|
|
1534
|
+
if (decisions.some((decision) => decision.decidedByMemberId === memberId)) {
|
|
1535
|
+
return true;
|
|
1536
|
+
}
|
|
1537
|
+
// Members who received any notification for this instance (e.g. ad-hoc
|
|
1538
|
+
// stage / completion notify recipients, CC'd service-task recipients) may
|
|
1539
|
+
// open the instance they were notified about.
|
|
1540
|
+
const notification = await this.notificationRepository.findOne({
|
|
1541
|
+
where: { instanceId: instance.id, recipientMemberId: memberId },
|
|
1542
|
+
});
|
|
1543
|
+
return Boolean(notification);
|
|
945
1544
|
}
|
|
946
1545
|
async processActiveToken(manager, instance, token) {
|
|
947
1546
|
const node = readWorkflowNodeOrThrow(instance.workflowSnapshot, token.currentNodeId);
|
|
@@ -1022,6 +1621,8 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
1022
1621
|
instance: completedInstance,
|
|
1023
1622
|
manager,
|
|
1024
1623
|
});
|
|
1624
|
+
await this.dispatchAdhocCompletionNotifications(manager, completedInstance, workflow_engine_enums_1.ApprovalInstanceStateEnum.APPROVED);
|
|
1625
|
+
await this.cancelPendingAdhocDirectives(manager, instance.id, null);
|
|
1025
1626
|
}
|
|
1026
1627
|
async isNodeEntryReady(manager, instance, token, node) {
|
|
1027
1628
|
if (node.type === 'startEvent') {
|
|
@@ -1256,6 +1857,8 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
1256
1857
|
manager,
|
|
1257
1858
|
});
|
|
1258
1859
|
}
|
|
1860
|
+
await this.dispatchAdhocCompletionNotifications(manager, completedInstance, instanceState);
|
|
1861
|
+
await this.cancelPendingAdhocDirectives(manager, instance.id, null);
|
|
1259
1862
|
}
|
|
1260
1863
|
await activityRepository.save(activityRepository.create({
|
|
1261
1864
|
actorMemberId: null,
|
|
@@ -1277,6 +1880,7 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
1277
1880
|
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
1278
1881
|
const existingTask = await taskRepository.findOne({
|
|
1279
1882
|
where: {
|
|
1883
|
+
isAdhoc: false,
|
|
1280
1884
|
nodeId: node.id,
|
|
1281
1885
|
tokenId: token.id,
|
|
1282
1886
|
},
|
|
@@ -1352,6 +1956,9 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
1352
1956
|
}),
|
|
1353
1957
|
});
|
|
1354
1958
|
}, Promise.resolve());
|
|
1959
|
+
// Pending ad-hoc countersign directives attach to the next user task
|
|
1960
|
+
// created after they were requested — spawn their parallel tasks now.
|
|
1961
|
+
await this.spawnCountersignTasksForNode(manager, instance, token, node, now);
|
|
1355
1962
|
}
|
|
1356
1963
|
async markTaskCandidateDecision({ action, candidate, candidates, decidedAt, manager, task, }) {
|
|
1357
1964
|
const taskCandidateRepository = manager.getRepository(task_candidate_entity_1.TaskCandidateEntity);
|
|
@@ -1505,12 +2112,19 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
1505
2112
|
}
|
|
1506
2113
|
async resolveRuntimeTaskCandidates(manager, instance, node) {
|
|
1507
2114
|
const resolvedCandidates = await this.resolveApproverResolver(manager, instance, node.data.approverResolver, `簽核節點「${node.data.label}」`);
|
|
2115
|
+
const uniqueCandidates = await this.applyDelegationToResolvedCandidates(instance, node.id, resolvedCandidates);
|
|
2116
|
+
if (uniqueCandidates.length === 0) {
|
|
2117
|
+
throw new common_1.ConflictException(`簽核節點「${node.data.label}」 did not resolve to any member id`);
|
|
2118
|
+
}
|
|
2119
|
+
return uniqueCandidates;
|
|
2120
|
+
}
|
|
2121
|
+
async applyDelegationToResolvedCandidates(instance, nodeId, resolvedCandidates) {
|
|
1508
2122
|
const context = {
|
|
1509
2123
|
formData: instance.formData,
|
|
1510
2124
|
initiatorMemberId: instance.initiatorMemberId,
|
|
1511
2125
|
initiatorMetadataSnapshot: instance.initiatorMetadataSnapshot,
|
|
1512
2126
|
instanceId: instance.id,
|
|
1513
|
-
nodeId
|
|
2127
|
+
nodeId,
|
|
1514
2128
|
state: instance.state,
|
|
1515
2129
|
templateId: instance.templateId,
|
|
1516
2130
|
templateVersionId: instance.templateVersionId,
|
|
@@ -1525,11 +2139,7 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
1525
2139
|
sourceType: candidate.sourceType,
|
|
1526
2140
|
};
|
|
1527
2141
|
}));
|
|
1528
|
-
|
|
1529
|
-
if (uniqueCandidates.length === 0) {
|
|
1530
|
-
throw new common_1.ConflictException(`簽核節點「${node.data.label}」 did not resolve to any member id`);
|
|
1531
|
-
}
|
|
1532
|
-
return uniqueCandidates;
|
|
2142
|
+
return uniqueRuntimeCandidates(delegatedCandidates);
|
|
1533
2143
|
}
|
|
1534
2144
|
async resolveApproverResolver(manager, instance, resolver, label) {
|
|
1535
2145
|
if (resolver.type === 'DIRECT') {
|
|
@@ -1826,27 +2436,25 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
1826
2436
|
}));
|
|
1827
2437
|
}
|
|
1828
2438
|
async rejectInstance(manager, instance, task, rejectedAt) {
|
|
1829
|
-
const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
|
|
1830
2439
|
const instanceRepository = manager.getRepository(approval_instance_entity_1.ApprovalInstanceEntity);
|
|
1831
|
-
|
|
1832
|
-
|
|
2440
|
+
// Dispatch pending ad-hoc notifications for the rejecting node before the
|
|
2441
|
+
// remaining directives are cancelled by the terminal cleanup below.
|
|
2442
|
+
await this.dispatchAdhocStageNotifications(manager, instance, task.nodeId, 'REJECTED');
|
|
2443
|
+
// Consume open tokens and cancel any still-open tasks (parallel branches,
|
|
2444
|
+
// ad-hoc countersign / pre-approval tasks) so nothing actionable lingers
|
|
2445
|
+
// on a rejected instance.
|
|
2446
|
+
await this.consumeOpenRuntimeState(manager, instance, rejectedAt, workflow_engine_enums_1.TaskStatusEnum.CANCELLED);
|
|
2447
|
+
await this.notificationService.supersedeInstanceTaskNotifications({
|
|
2448
|
+
instanceId: instance.id,
|
|
2449
|
+
manager,
|
|
1833
2450
|
});
|
|
1834
|
-
const
|
|
1835
|
-
.filter((token) => token.status === workflow_engine_enums_1.WorkflowTokenStatusEnum.ACTIVE ||
|
|
1836
|
-
token.status === workflow_engine_enums_1.WorkflowTokenStatusEnum.WAITING)
|
|
1837
|
-
.map((token) => ({
|
|
1838
|
-
...token,
|
|
1839
|
-
consumedAt: rejectedAt,
|
|
1840
|
-
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.CONSUMED,
|
|
1841
|
-
}));
|
|
1842
|
-
if (consumedTokens.length) {
|
|
1843
|
-
await tokenRepository.save(consumedTokens);
|
|
1844
|
-
}
|
|
1845
|
-
await instanceRepository.save({
|
|
2451
|
+
const rejectedInstance = await instanceRepository.save({
|
|
1846
2452
|
...instance,
|
|
1847
2453
|
completedAt: rejectedAt,
|
|
1848
2454
|
state: workflow_engine_enums_1.ApprovalInstanceStateEnum.REJECTED,
|
|
1849
2455
|
});
|
|
2456
|
+
await this.dispatchAdhocCompletionNotifications(manager, rejectedInstance, workflow_engine_enums_1.ApprovalInstanceStateEnum.REJECTED);
|
|
2457
|
+
await this.cancelPendingAdhocDirectives(manager, instance.id, null);
|
|
1850
2458
|
await manager.getRepository(activity_log_entity_1.ActivityLogEntity).save(manager.getRepository(activity_log_entity_1.ActivityLogEntity).create({
|
|
1851
2459
|
actorMemberId: task.assigneeMemberId,
|
|
1852
2460
|
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TOKEN_ADVANCED,
|
|
@@ -1867,6 +2475,14 @@ let WorkflowEngineService = class WorkflowEngineService {
|
|
|
1867
2475
|
const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
|
|
1868
2476
|
const instanceRepository = manager.getRepository(approval_instance_entity_1.ApprovalInstanceEntity);
|
|
1869
2477
|
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
2478
|
+
// The returning node's stage has ended (outcome: RETURNED) — dispatch its
|
|
2479
|
+
// pending ad-hoc stage notifications, then drop flow-affecting ad-hoc
|
|
2480
|
+
// directives so they cannot replay after the return/resubmit cycle.
|
|
2481
|
+
await this.dispatchAdhocStageNotifications(manager, instance, task.nodeId, 'RETURNED');
|
|
2482
|
+
await this.cancelPendingAdhocDirectives(manager, instance.id, [
|
|
2483
|
+
adhoc_enums_1.AdhocDirectiveTypeEnum.COUNTERSIGN,
|
|
2484
|
+
adhoc_enums_1.AdhocDirectiveTypeEnum.PRE_APPROVAL,
|
|
2485
|
+
]);
|
|
1870
2486
|
await this.consumeOpenRuntimeState(manager, instance, returnedAt, workflow_engine_enums_1.TaskStatusEnum.CANCELLED);
|
|
1871
2487
|
const returnedInstance = await instanceRepository.save({
|
|
1872
2488
|
...instance,
|
|
@@ -2096,11 +2712,12 @@ exports.WorkflowEngineService = WorkflowEngineService = tslib_1.__decorate([
|
|
|
2096
2712
|
tslib_1.__param(4, (0, typeorm_1.InjectRepository)(task_decision_entity_1.TaskDecisionEntity)),
|
|
2097
2713
|
tslib_1.__param(5, (0, typeorm_1.InjectRepository)(notification_entity_1.NotificationEntity)),
|
|
2098
2714
|
tslib_1.__param(6, (0, typeorm_1.InjectRepository)(activity_log_entity_1.ActivityLogEntity)),
|
|
2099
|
-
tslib_1.__param(7, (0, typeorm_1.InjectRepository)(
|
|
2100
|
-
tslib_1.__param(8, (0, typeorm_1.InjectRepository)(
|
|
2101
|
-
tslib_1.__param(9, (0, typeorm_1.InjectRepository)(
|
|
2102
|
-
tslib_1.__param(
|
|
2103
|
-
tslib_1.__param(
|
|
2715
|
+
tslib_1.__param(7, (0, typeorm_1.InjectRepository)(adhoc_directive_entity_1.AdhocDirectiveEntity)),
|
|
2716
|
+
tslib_1.__param(8, (0, typeorm_1.InjectRepository)(approval_template_entity_1.ApprovalTemplateEntity)),
|
|
2717
|
+
tslib_1.__param(9, (0, typeorm_1.InjectRepository)(approval_template_version_entity_1.ApprovalTemplateVersionEntity)),
|
|
2718
|
+
tslib_1.__param(10, (0, typeorm_1.InjectRepository)(form_definition_version_entity_1.FormDefinitionVersionEntity)),
|
|
2719
|
+
tslib_1.__param(16, (0, common_1.Optional)()),
|
|
2720
|
+
tslib_1.__param(16, (0, common_1.Inject)(workflow_service_task_dispatcher_token_1.BPM_WORKFLOW_SERVICE_TASK_DISPATCHER)),
|
|
2104
2721
|
tslib_1.__metadata("design:paramtypes", [typeorm_2.Repository,
|
|
2105
2722
|
typeorm_2.Repository,
|
|
2106
2723
|
typeorm_2.Repository,
|
|
@@ -2111,6 +2728,7 @@ exports.WorkflowEngineService = WorkflowEngineService = tslib_1.__decorate([
|
|
|
2111
2728
|
typeorm_2.Repository,
|
|
2112
2729
|
typeorm_2.Repository,
|
|
2113
2730
|
typeorm_2.Repository,
|
|
2731
|
+
typeorm_2.Repository,
|
|
2114
2732
|
attachment_service_1.AttachmentService,
|
|
2115
2733
|
condition_service_1.ConditionService,
|
|
2116
2734
|
delegation_service_1.DelegationService,
|
|
@@ -2515,6 +3133,152 @@ async function executeWebhookServiceAction(serviceTaskDispatcher, action, payloa
|
|
|
2515
3133
|
};
|
|
2516
3134
|
}
|
|
2517
3135
|
}
|
|
3136
|
+
async function executeAdhocWebhookDispatch(serviceTaskDispatcher, target, payload) {
|
|
3137
|
+
if (!target.webhookUrl) {
|
|
3138
|
+
return { error: 'Webhook URL is missing', ok: false, status: null };
|
|
3139
|
+
}
|
|
3140
|
+
try {
|
|
3141
|
+
return await serviceTaskDispatcher.dispatchWebhook({
|
|
3142
|
+
headers: target.webhookHeaders,
|
|
3143
|
+
payload,
|
|
3144
|
+
url: target.webhookUrl,
|
|
3145
|
+
});
|
|
3146
|
+
}
|
|
3147
|
+
catch (error) {
|
|
3148
|
+
return {
|
|
3149
|
+
error: error instanceof Error ? error.message : 'Unknown webhook error',
|
|
3150
|
+
ok: false,
|
|
3151
|
+
status: null,
|
|
3152
|
+
};
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
function buildAdhocTargetValue(input) {
|
|
3156
|
+
if (input.kind === adhoc_enums_1.AdhocTargetKindEnum.MEMBER) {
|
|
3157
|
+
const memberIds = uniqueTexts([...(input.memberIds ?? [])]);
|
|
3158
|
+
if (memberIds.length === 0) {
|
|
3159
|
+
throw new common_1.BadRequestException('memberIds is required for a MEMBER ad-hoc target');
|
|
3160
|
+
}
|
|
3161
|
+
return { kind: input.kind, memberIds };
|
|
3162
|
+
}
|
|
3163
|
+
if (input.kind === adhoc_enums_1.AdhocTargetKindEnum.POSITION) {
|
|
3164
|
+
const positionId = input.positionId?.trim();
|
|
3165
|
+
if (!positionId) {
|
|
3166
|
+
throw new common_1.BadRequestException('positionId is required for a POSITION ad-hoc target');
|
|
3167
|
+
}
|
|
3168
|
+
return { kind: input.kind, positionId };
|
|
3169
|
+
}
|
|
3170
|
+
if (input.kind === adhoc_enums_1.AdhocTargetKindEnum.ORG_UNIT_MEMBER) {
|
|
3171
|
+
const orgUnitId = input.orgUnitId?.trim();
|
|
3172
|
+
if (!orgUnitId) {
|
|
3173
|
+
throw new common_1.BadRequestException('orgUnitId is required for an ORG_UNIT_MEMBER ad-hoc target');
|
|
3174
|
+
}
|
|
3175
|
+
return {
|
|
3176
|
+
includeDescendants: Boolean(input.includeDescendants),
|
|
3177
|
+
kind: input.kind,
|
|
3178
|
+
orgUnitId,
|
|
3179
|
+
};
|
|
3180
|
+
}
|
|
3181
|
+
const webhookUrl = input.webhookUrl?.trim();
|
|
3182
|
+
if (!webhookUrl) {
|
|
3183
|
+
throw new common_1.BadRequestException('webhookUrl is required for a WEBHOOK ad-hoc target');
|
|
3184
|
+
}
|
|
3185
|
+
return {
|
|
3186
|
+
kind: input.kind,
|
|
3187
|
+
webhookHeaders: input.webhookHeadersJson
|
|
3188
|
+
? parseAdhocWebhookHeaders(input.webhookHeadersJson)
|
|
3189
|
+
: undefined,
|
|
3190
|
+
webhookUrl,
|
|
3191
|
+
};
|
|
3192
|
+
}
|
|
3193
|
+
function parseAdhocWebhookHeaders(json) {
|
|
3194
|
+
try {
|
|
3195
|
+
const parsed = JSON.parse(json);
|
|
3196
|
+
if (!isRecord(parsed)) {
|
|
3197
|
+
throw new common_1.BadRequestException('webhookHeadersJson must be a JSON object of string values');
|
|
3198
|
+
}
|
|
3199
|
+
return Object.entries(parsed).reduce((accumulator, [key, value]) => ({
|
|
3200
|
+
...accumulator,
|
|
3201
|
+
...(typeof value === 'string' ? { [key]: value } : {}),
|
|
3202
|
+
}), {});
|
|
3203
|
+
}
|
|
3204
|
+
catch (error) {
|
|
3205
|
+
if (error instanceof common_1.BadRequestException) {
|
|
3206
|
+
throw error;
|
|
3207
|
+
}
|
|
3208
|
+
throw new common_1.BadRequestException('webhookHeadersJson is not valid JSON');
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
function readAdhocTargetValue(directive) {
|
|
3212
|
+
const value = directive.targetValue;
|
|
3213
|
+
const kind = directive.targetKind;
|
|
3214
|
+
if (kind === adhoc_enums_1.AdhocTargetKindEnum.MEMBER) {
|
|
3215
|
+
return {
|
|
3216
|
+
kind,
|
|
3217
|
+
memberIds: Array.isArray(value.memberIds)
|
|
3218
|
+
? value.memberIds.filter((memberId) => typeof memberId === 'string')
|
|
3219
|
+
: [],
|
|
3220
|
+
};
|
|
3221
|
+
}
|
|
3222
|
+
if (kind === adhoc_enums_1.AdhocTargetKindEnum.POSITION) {
|
|
3223
|
+
return {
|
|
3224
|
+
kind,
|
|
3225
|
+
positionId: typeof value.positionId === 'string' ? value.positionId : '',
|
|
3226
|
+
};
|
|
3227
|
+
}
|
|
3228
|
+
if (kind === adhoc_enums_1.AdhocTargetKindEnum.ORG_UNIT_MEMBER) {
|
|
3229
|
+
return {
|
|
3230
|
+
includeDescendants: Boolean(value.includeDescendants),
|
|
3231
|
+
kind,
|
|
3232
|
+
orgUnitId: typeof value.orgUnitId === 'string' ? value.orgUnitId : '',
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
return {
|
|
3236
|
+
kind,
|
|
3237
|
+
webhookHeaders: isRecord(value.webhookHeaders)
|
|
3238
|
+
? Object.entries(value.webhookHeaders).reduce((accumulator, [key, headerValue]) => ({
|
|
3239
|
+
...accumulator,
|
|
3240
|
+
...(typeof headerValue === 'string'
|
|
3241
|
+
? { [key]: headerValue }
|
|
3242
|
+
: {}),
|
|
3243
|
+
}), {})
|
|
3244
|
+
: undefined,
|
|
3245
|
+
webhookUrl: typeof value.webhookUrl === 'string' ? value.webhookUrl : '',
|
|
3246
|
+
};
|
|
3247
|
+
}
|
|
3248
|
+
function buildAdhocApproverResolver(target) {
|
|
3249
|
+
if (target.kind === adhoc_enums_1.AdhocTargetKindEnum.MEMBER) {
|
|
3250
|
+
return { memberIds: target.memberIds ?? [], type: 'DIRECT' };
|
|
3251
|
+
}
|
|
3252
|
+
if (target.kind === adhoc_enums_1.AdhocTargetKindEnum.POSITION) {
|
|
3253
|
+
return { positionId: target.positionId ?? '', type: 'POSITION' };
|
|
3254
|
+
}
|
|
3255
|
+
if (target.kind === adhoc_enums_1.AdhocTargetKindEnum.ORG_UNIT_MEMBER) {
|
|
3256
|
+
return {
|
|
3257
|
+
includeDescendants: target.includeDescendants,
|
|
3258
|
+
orgUnitId: target.orgUnitId ?? '',
|
|
3259
|
+
type: 'ORG_UNIT_MEMBER',
|
|
3260
|
+
};
|
|
3261
|
+
}
|
|
3262
|
+
throw new common_1.BadRequestException('WEBHOOK ad-hoc targets cannot be used as approvers');
|
|
3263
|
+
}
|
|
3264
|
+
function readAdhocStageOutcomeLabel(outcome) {
|
|
3265
|
+
if (outcome === 'APPROVED') {
|
|
3266
|
+
return '通過';
|
|
3267
|
+
}
|
|
3268
|
+
if (outcome === 'REJECTED') {
|
|
3269
|
+
return '拒絕';
|
|
3270
|
+
}
|
|
3271
|
+
return '退回';
|
|
3272
|
+
}
|
|
3273
|
+
function readInstanceFinalStateLabel(finalState) {
|
|
3274
|
+
if (finalState === workflow_engine_enums_1.ApprovalInstanceStateEnum.APPROVED) {
|
|
3275
|
+
return '核准';
|
|
3276
|
+
}
|
|
3277
|
+
if (finalState === workflow_engine_enums_1.ApprovalInstanceStateEnum.REJECTED) {
|
|
3278
|
+
return '拒絕';
|
|
3279
|
+
}
|
|
3280
|
+
return '取消';
|
|
3281
|
+
}
|
|
2518
3282
|
function readManagerMemberIdFromInitiatorSnapshot(initiatorMetadataSnapshot, levelsUp) {
|
|
2519
3283
|
const managerChain = initiatorMetadataSnapshot.managerChain;
|
|
2520
3284
|
if (Array.isArray(managerChain)) {
|
|
@@ -2782,4 +3546,16 @@ function readDryRunStepMessage(node, entryConditionMet) {
|
|
|
2782
3546
|
}
|
|
2783
3547
|
return '節點條件通過。';
|
|
2784
3548
|
}
|
|
3549
|
+
function mapDecisionToResolution(action) {
|
|
3550
|
+
switch (action) {
|
|
3551
|
+
case workflow_engine_enums_1.TaskDecisionActionEnum.APPROVED:
|
|
3552
|
+
return notification_enums_1.NotificationResolutionEnum.APPROVED;
|
|
3553
|
+
case workflow_engine_enums_1.TaskDecisionActionEnum.REJECTED:
|
|
3554
|
+
return notification_enums_1.NotificationResolutionEnum.REJECTED;
|
|
3555
|
+
case workflow_engine_enums_1.TaskDecisionActionEnum.RETURNED:
|
|
3556
|
+
return notification_enums_1.NotificationResolutionEnum.RETURNED;
|
|
3557
|
+
case workflow_engine_enums_1.TaskDecisionActionEnum.TRANSFERRED:
|
|
3558
|
+
return notification_enums_1.NotificationResolutionEnum.TRANSFERRED;
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
2785
3561
|
//# sourceMappingURL=workflow-engine.service.js.map
|