@rytass/bpm-core-nestjs-module 0.6.0 → 0.9.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.
Files changed (126) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/README.md +60 -1
  3. package/package.json +17 -3
  4. package/src/index.d.ts +2 -0
  5. package/src/index.js +2 -0
  6. package/src/index.js.map +1 -1
  7. package/src/lib/bpm/bpm-root.module.d.ts +33 -0
  8. package/src/lib/bpm/bpm-root.module.js +20 -0
  9. package/src/lib/bpm/bpm-root.module.js.map +1 -1
  10. package/src/lib/calendar/business-calendar.provider.d.ts +11 -0
  11. package/src/lib/calendar/business-calendar.provider.js +20 -0
  12. package/src/lib/calendar/business-calendar.provider.js.map +1 -0
  13. package/src/lib/calendar/business-calendar.token.d.ts +38 -0
  14. package/src/lib/calendar/business-calendar.token.js +5 -0
  15. package/src/lib/calendar/business-calendar.token.js.map +1 -0
  16. package/src/lib/calendar/calendar.module.d.ts +22 -0
  17. package/src/lib/calendar/calendar.module.js +33 -0
  18. package/src/lib/calendar/calendar.module.js.map +1 -0
  19. package/src/lib/calendar/index.d.ts +5 -0
  20. package/src/lib/calendar/index.js +9 -0
  21. package/src/lib/calendar/index.js.map +1 -0
  22. package/src/lib/calendar/sla-schedule.service.d.ts +33 -0
  23. package/src/lib/calendar/sla-schedule.service.js +94 -0
  24. package/src/lib/calendar/sla-schedule.service.js.map +1 -0
  25. package/src/lib/calendar/weekday-business-calendar.d.ts +15 -0
  26. package/src/lib/calendar/weekday-business-calendar.js +28 -0
  27. package/src/lib/calendar/weekday-business-calendar.js.map +1 -0
  28. package/src/lib/common/iso-duration.d.ts +11 -0
  29. package/src/lib/common/iso-duration.js +13 -4
  30. package/src/lib/common/iso-duration.js.map +1 -1
  31. package/src/lib/form/form-schema.validator.js +177 -10
  32. package/src/lib/form/form-schema.validator.js.map +1 -1
  33. package/src/lib/form/form.service.d.ts +19 -1
  34. package/src/lib/form/form.service.js +56 -6
  35. package/src/lib/form/form.service.js.map +1 -1
  36. package/src/lib/form-data-source/form-data-source-value-resolver.service.d.ts +16 -0
  37. package/src/lib/form-data-source/form-data-source-value-resolver.service.js +401 -0
  38. package/src/lib/form-data-source/form-data-source-value-resolver.service.js.map +1 -0
  39. package/src/lib/form-data-source/form-data-source.errors.d.ts +34 -0
  40. package/src/lib/form-data-source/form-data-source.errors.js +43 -0
  41. package/src/lib/form-data-source/form-data-source.errors.js.map +1 -0
  42. package/src/lib/form-data-source/form-data-source.module.d.ts +9 -0
  43. package/src/lib/form-data-source/form-data-source.module.js +55 -0
  44. package/src/lib/form-data-source/form-data-source.module.js.map +1 -0
  45. package/src/lib/form-data-source/form-data-source.queries.d.ts +57 -0
  46. package/src/lib/form-data-source/form-data-source.queries.js +293 -0
  47. package/src/lib/form-data-source/form-data-source.queries.js.map +1 -0
  48. package/src/lib/form-data-source/form-data-source.service.d.ts +72 -0
  49. package/src/lib/form-data-source/form-data-source.service.js +646 -0
  50. package/src/lib/form-data-source/form-data-source.service.js.map +1 -0
  51. package/src/lib/form-data-source/form-data-source.types.d.ts +88 -0
  52. package/src/lib/form-data-source/form-data-source.types.js +29 -0
  53. package/src/lib/form-data-source/form-data-source.types.js.map +1 -0
  54. package/src/lib/form-data-source/index.d.ts +5 -0
  55. package/src/lib/form-data-source/index.js +9 -0
  56. package/src/lib/form-data-source/index.js.map +1 -0
  57. package/src/lib/migrations/0000000018000-notification-archive.d.ts +6 -0
  58. package/src/lib/migrations/0000000018000-notification-archive.js +28 -0
  59. package/src/lib/migrations/0000000018000-notification-archive.js.map +1 -0
  60. package/src/lib/migrations/0000000019000-approval-template-activation.d.ts +6 -0
  61. package/src/lib/migrations/0000000019000-approval-template-activation.js +29 -0
  62. package/src/lib/migrations/0000000019000-approval-template-activation.js.map +1 -0
  63. package/src/lib/migrations/0000000020000-form-data-option-snapshots.d.ts +6 -0
  64. package/src/lib/migrations/0000000020000-form-data-option-snapshots.js +22 -0
  65. package/src/lib/migrations/0000000020000-form-data-option-snapshots.js.map +1 -0
  66. package/src/lib/migrations/index.d.ts +3 -0
  67. package/src/lib/migrations/index.js +9 -0
  68. package/src/lib/migrations/index.js.map +1 -1
  69. package/src/lib/notification/index.d.ts +3 -0
  70. package/src/lib/notification/index.js +3 -0
  71. package/src/lib/notification/index.js.map +1 -1
  72. package/src/lib/notification/notification-observer.token.d.ts +52 -0
  73. package/src/lib/notification/notification-observer.token.js +5 -0
  74. package/src/lib/notification/notification-observer.token.js.map +1 -0
  75. package/src/lib/notification/notification-options.d.ts +10 -0
  76. package/src/lib/notification/notification-options.js +19 -0
  77. package/src/lib/notification/notification-options.js.map +1 -1
  78. package/src/lib/notification/notification.entity.d.ts +11 -0
  79. package/src/lib/notification/notification.entity.js +17 -0
  80. package/src/lib/notification/notification.entity.js.map +1 -1
  81. package/src/lib/notification/notification.enums.d.ts +13 -0
  82. package/src/lib/notification/notification.enums.js +14 -1
  83. package/src/lib/notification/notification.enums.js.map +1 -1
  84. package/src/lib/notification/notification.mutations.d.ts +2 -0
  85. package/src/lib/notification/notification.mutations.js +28 -0
  86. package/src/lib/notification/notification.mutations.js.map +1 -1
  87. package/src/lib/notification/notification.queries.d.ts +2 -2
  88. package/src/lib/notification/notification.queries.js +12 -8
  89. package/src/lib/notification/notification.queries.js.map +1 -1
  90. package/src/lib/notification/notification.service.d.ts +42 -2
  91. package/src/lib/notification/notification.service.js +135 -4
  92. package/src/lib/notification/notification.service.js.map +1 -1
  93. package/src/lib/template/approval-template.entity.d.ts +13 -0
  94. package/src/lib/template/approval-template.entity.js +12 -1
  95. package/src/lib/template/approval-template.entity.js.map +1 -1
  96. package/src/lib/template/template.enums.d.ts +5 -0
  97. package/src/lib/template/template.enums.js +10 -1
  98. package/src/lib/template/template.enums.js.map +1 -1
  99. package/src/lib/template/template.mutations.d.ts +2 -0
  100. package/src/lib/template/template.mutations.js +20 -0
  101. package/src/lib/template/template.mutations.js.map +1 -1
  102. package/src/lib/template/template.queries.d.ts +3 -3
  103. package/src/lib/template/template.queries.js +14 -4
  104. package/src/lib/template/template.queries.js.map +1 -1
  105. package/src/lib/template/template.service.d.ts +7 -2
  106. package/src/lib/template/template.service.js +56 -11
  107. package/src/lib/template/template.service.js.map +1 -1
  108. package/src/lib/template/workflow-definition.validator.js +86 -1
  109. package/src/lib/template/workflow-definition.validator.js.map +1 -1
  110. package/src/lib/workflow-engine/approval-instance.entity.d.ts +3 -0
  111. package/src/lib/workflow-engine/approval-instance.entity.js +15 -0
  112. package/src/lib/workflow-engine/approval-instance.entity.js.map +1 -1
  113. package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.d.ts +5 -1
  114. package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.js +6 -2
  115. package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.js.map +1 -1
  116. package/src/lib/workflow-engine/dto/submit-approval-instance.input.d.ts +5 -1
  117. package/src/lib/workflow-engine/dto/submit-approval-instance.input.js +6 -2
  118. package/src/lib/workflow-engine/dto/submit-approval-instance.input.js.map +1 -1
  119. package/src/lib/workflow-engine/workflow-engine.mutations.d.ts +3 -2
  120. package/src/lib/workflow-engine/workflow-engine.mutations.js +8 -6
  121. package/src/lib/workflow-engine/workflow-engine.mutations.js.map +1 -1
  122. package/src/lib/workflow-engine/workflow-engine.service.d.ts +37 -4
  123. package/src/lib/workflow-engine/workflow-engine.service.js +245 -15
  124. package/src/lib/workflow-engine/workflow-engine.service.js.map +1 -1
  125. package/src/lib/workflow-engine/workflow-engine.tokens.d.ts +2 -1
  126. package/src/lib/workflow-engine/workflow-engine.tokens.js.map +1 -1
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WorkflowEngineService = void 0;
3
+ exports.WorkflowEngineService = exports.MANUAL_TRANSFER_DELEGATION_REASON = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const form_1 = require("@rytass/bpm-core-shared/form");
5
6
  const common_1 = require("@nestjs/common");
6
7
  const typeorm_1 = require("@nestjs/typeorm");
7
8
  const typeorm_2 = require("typeorm");
@@ -9,6 +10,9 @@ const attachment_service_1 = require("../attachment/attachment.service");
9
10
  const delegation_service_1 = require("../delegation/delegation.service");
10
11
  const form_definition_version_entity_1 = require("../form/form-definition-version.entity");
11
12
  const form_enums_1 = require("../form/form.enums");
13
+ const form_data_source_errors_1 = require("../form-data-source/form-data-source.errors");
14
+ const form_data_source_types_1 = require("../form-data-source/form-data-source.types");
15
+ const sla_schedule_service_1 = require("../calendar/sla-schedule.service");
12
16
  const notification_service_1 = require("../notification/notification.service");
13
17
  const notification_entity_1 = require("../notification/notification.entity");
14
18
  const notification_enums_1 = require("../notification/notification.enums");
@@ -36,6 +40,11 @@ const workflow_token_entity_1 = require("./workflow-token.entity");
36
40
  const MAX_PROCESSING_STEPS = 500;
37
41
  const DEFAULT_APPROVAL_HISTORY_TASK_LIMIT = 50;
38
42
  const SYSTEM_DRY_RUN_INSTANCE_ID = 'dry-run-instance';
43
+ /**
44
+ * Delegation-chain reason recorded for an approver-initiated transfer, as
45
+ * opposed to {@link SLA_ESCALATION_DELEGATION_REASON}.
46
+ */
47
+ exports.MANUAL_TRANSFER_DELEGATION_REASON = 'MANUAL_TRANSFER';
39
48
  const WORKFLOW_READ_ALL_PERMISSIONS = new Set([
40
49
  'bpm:*',
41
50
  'bpm.workflow.read_all',
@@ -43,7 +52,7 @@ const WORKFLOW_READ_ALL_PERMISSIONS = new Set([
43
52
  'workflow.read_all',
44
53
  ]);
45
54
  let WorkflowEngineService = class WorkflowEngineService {
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()) {
55
+ constructor(approvalInstanceRepository, workflowTokenRepository, taskRepository, taskCandidateRepository, taskDecisionRepository, notificationRepository, activityLogRepository, adhocDirectiveRepository, approvalTemplateRepository, approvalTemplateVersionRepository, formDefinitionVersionRepository, attachmentService, conditionService, delegationService, notificationService, signatureService, slaScheduleService, serviceTaskDispatcher = new workflow_service_task_dispatcher_token_1.DefaultWorkflowServiceTaskDispatcher(), formDataSourceValueResolver) {
47
56
  this.approvalInstanceRepository = approvalInstanceRepository;
48
57
  this.workflowTokenRepository = workflowTokenRepository;
49
58
  this.taskRepository = taskRepository;
@@ -60,17 +69,28 @@ let WorkflowEngineService = class WorkflowEngineService {
60
69
  this.delegationService = delegationService;
61
70
  this.notificationService = notificationService;
62
71
  this.signatureService = signatureService;
72
+ this.slaScheduleService = slaScheduleService;
63
73
  this.serviceTaskDispatcher = serviceTaskDispatcher;
74
+ this.formDataSourceValueResolver = formDataSourceValueResolver;
64
75
  }
65
- async submitApprovalInstance(input) {
76
+ async submitApprovalInstance(input, authContext) {
66
77
  const formData = parseJsonObject(input.formDataJson, 'formDataJson');
67
78
  const template = await this.getTemplateOrThrow(input.templateId);
79
+ if (!template.isActive) {
80
+ throw new common_1.ConflictException('Approval template is deactivated');
81
+ }
68
82
  if (!template.currentVersionId) {
69
83
  throw new common_1.ConflictException('Approval template does not have a published version');
70
84
  }
71
85
  const templateVersion = await this.getPublishedTemplateVersionOrThrow(template.currentVersionId);
72
86
  const formDefinitionVersion = await this.getPublishedFormVersionOrThrow(templateVersion.formDefinitionVersionId);
73
87
  validateSubmittedFormData(formDefinitionVersion.schema, formData);
88
+ const formDataOptionSnapshot = await this.resolveFormDataOptionSnapshots({
89
+ authContext: authContext ?? createWorkflowAuthContext(input.initiatorMemberId),
90
+ formData,
91
+ revalidateAll: true,
92
+ schema: formDefinitionVersion.schema,
93
+ });
74
94
  const initiatorMetadataSnapshot = input.initiatorMetadataSnapshotJson
75
95
  ? parseJsonObject(input.initiatorMetadataSnapshotJson, 'initiatorMetadataSnapshotJson')
76
96
  : await this.readDefaultInitiatorMetadataSnapshot(input.initiatorMemberId);
@@ -89,6 +109,7 @@ let WorkflowEngineService = class WorkflowEngineService {
89
109
  const instance = await instanceRepository.save(instanceRepository.create({
90
110
  completedAt: null,
91
111
  formData,
112
+ formDataOptionSnapshot,
92
113
  formDefinitionSnapshot: {
93
114
  formDefinitionVersionId: formDefinitionVersion.id,
94
115
  schema: formDefinitionVersion.schema,
@@ -142,6 +163,48 @@ let WorkflowEngineService = class WorkflowEngineService {
142
163
  return instance;
143
164
  });
144
165
  }
166
+ async resolveFormDataOptionSnapshots({ authContext, formData, previousFormData, previousSnapshots, revalidateAll, schema, }) {
167
+ if (!this.formDataSourceValueResolver) {
168
+ assertNoUnresolvableDynamicValues(schema, formData);
169
+ return previousSnapshots ?? {};
170
+ }
171
+ return this.formDataSourceValueResolver.resolveFormDataOptionSnapshots({
172
+ authContext,
173
+ formData,
174
+ previousFormData,
175
+ previousSnapshots,
176
+ revalidateAll,
177
+ schema,
178
+ });
179
+ }
180
+ async prepareResubmitFormDataOptions(input, formData, authContext) {
181
+ const instance = await this.approvalInstanceRepository.findOne({
182
+ where: { id: input.instanceId },
183
+ });
184
+ if (!instance) {
185
+ throw new common_1.NotFoundException(`Approval instance ${input.instanceId} was not found`);
186
+ }
187
+ if (instance.initiatorMemberId !== input.initiatorMemberId) {
188
+ throw new common_1.ConflictException(`Approval instance ${instance.id} can only be resubmitted by its initiator`);
189
+ }
190
+ if (instance.state !== workflow_engine_enums_1.ApprovalInstanceStateEnum.RETURNED) {
191
+ throw new common_1.ConflictException(`Approval instance ${instance.id} is not returned`);
192
+ }
193
+ const template = await this.getTemplateOrThrow(instance.templateId);
194
+ if (!template.isActive) {
195
+ throw new common_1.ConflictException('Approval template is deactivated');
196
+ }
197
+ const schema = readFormDefinitionSnapshotSchema(instance.formDefinitionSnapshot);
198
+ validateSubmittedFormData(schema, formData);
199
+ const formDataOptionSnapshot = await this.resolveFormDataOptionSnapshots({
200
+ authContext,
201
+ formData,
202
+ previousFormData: instance.formData,
203
+ previousSnapshots: instance.formDataOptionSnapshot ?? {},
204
+ schema,
205
+ });
206
+ return { formDataOptionSnapshot, instance };
207
+ }
145
208
  async processInstance(instanceId) {
146
209
  await this.approvalInstanceRepository.manager.transaction(async (manager) => {
147
210
  await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
@@ -169,7 +232,7 @@ let WorkflowEngineService = class WorkflowEngineService {
169
232
  await this.processRunningInstance(manager, instance);
170
233
  });
171
234
  }
172
- async decideTask(input) {
235
+ async decideTask(input, options = {}) {
173
236
  return this.approvalInstanceRepository.manager.transaction(async (manager) => {
174
237
  const taskRepository = manager.getRepository(task_entity_1.TaskEntity);
175
238
  const taskCandidateRepository = manager.getRepository(task_candidate_entity_1.TaskCandidateEntity);
@@ -220,9 +283,30 @@ let WorkflowEngineService = class WorkflowEngineService {
220
283
  if (instance.state !== workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING) {
221
284
  throw new common_1.ConflictException(`Approval instance ${instance.id} is not running`);
222
285
  }
286
+ const taskNode = readWorkflowNodeOrThrow(instance.workflowSnapshot, task.nodeId);
287
+ if (taskNode.type !== 'userTask') {
288
+ throw new common_1.ConflictException(`Task ${task.id} is not bound to a user task node`);
289
+ }
290
+ if (input.action === workflow_engine_enums_1.TaskDecisionActionEnum.REJECTED &&
291
+ taskNode.data.allowReject === false) {
292
+ throw new common_1.ForbiddenException(`Workflow node ${task.nodeId} does not allow rejection`);
293
+ }
294
+ if (input.action === workflow_engine_enums_1.TaskDecisionActionEnum.TRANSFERRED &&
295
+ options.transferReason === undefined &&
296
+ taskNode.data.allowTransfer === false) {
297
+ throw new common_1.ForbiddenException(`Workflow node ${task.nodeId} does not allow transfer`);
298
+ }
223
299
  const returnToNodeId = input.action === workflow_engine_enums_1.TaskDecisionActionEnum.RETURNED
224
300
  ? readReturnTargetNodeId(instance.workflowSnapshot, task.nodeId, input.returnToNodeId)
225
301
  : null;
302
+ // Read from the instance snapshot, not the live template: templates
303
+ // that turn `requireComment` on later must not retroactively block
304
+ // in-flight instances started against the previous version.
305
+ if (input.action === workflow_engine_enums_1.TaskDecisionActionEnum.RETURNED &&
306
+ !decisionComment &&
307
+ isReturnCommentRequired(instance.workflowSnapshot, task.nodeId)) {
308
+ throw new common_1.BadRequestException(`Return decision comment is required by workflow node ${task.nodeId}`);
309
+ }
226
310
  const decidedAt = new Date();
227
311
  const claimedTask = await taskRepository.save({
228
312
  ...task,
@@ -261,7 +345,7 @@ let WorkflowEngineService = class WorkflowEngineService {
261
345
  transferToMemberId,
262
346
  }));
263
347
  if (input.action === workflow_engine_enums_1.TaskDecisionActionEnum.TRANSFERRED) {
264
- await this.transferTask(manager, instance, claimedTask, decision, signature, transferToMemberId, decisionComment, decidedAt, claimedCandidate);
348
+ await this.transferTask(manager, instance, claimedTask, decision, signature, transferToMemberId, decisionComment, decidedAt, claimedCandidate, options.transferReason ?? exports.MANUAL_TRANSFER_DELEGATION_REASON);
265
349
  await this.notificationService.resolveTaskNotifications({
266
350
  actingMemberId: input.decidedByMemberId,
267
351
  manager,
@@ -408,8 +492,11 @@ let WorkflowEngineService = class WorkflowEngineService {
408
492
  return cancelledInstance;
409
493
  });
410
494
  }
411
- async resubmitApprovalInstance(input) {
495
+ async resubmitApprovalInstance(input, authContext) {
412
496
  const formData = parseJsonObject(input.formDataJson, 'formDataJson');
497
+ const preparation = this.formDataSourceValueResolver
498
+ ? await this.prepareResubmitFormDataOptions(input, formData, authContext ?? createWorkflowAuthContext(input.initiatorMemberId))
499
+ : null;
413
500
  return this.approvalInstanceRepository.manager.transaction(async (manager) => {
414
501
  await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
415
502
  input.instanceId,
@@ -427,11 +514,28 @@ let WorkflowEngineService = class WorkflowEngineService {
427
514
  if (instance.state !== workflow_engine_enums_1.ApprovalInstanceStateEnum.RETURNED) {
428
515
  throw new common_1.ConflictException(`Approval instance ${instance.id} is not returned`);
429
516
  }
430
- validateSubmittedFormData(readFormDefinitionSnapshotSchema(instance.formDefinitionSnapshot), formData);
517
+ const template = await this.getTemplateOrThrow(instance.templateId);
518
+ if (!template.isActive) {
519
+ throw new common_1.ConflictException('Approval template is deactivated');
520
+ }
521
+ if (preparation &&
522
+ !isSameApprovalInstanceRevision(preparation.instance, instance)) {
523
+ throw new common_1.ConflictException('Approval instance changed while options were resolving; refresh and retry');
524
+ }
525
+ const resubmitSchema = readFormDefinitionSnapshotSchema(instance.formDefinitionSnapshot);
526
+ validateSubmittedFormData(resubmitSchema, formData);
527
+ if (!preparation) {
528
+ assertNoUnresolvableDynamicValues(resubmitSchema, formData);
529
+ }
431
530
  const resubmittedInstance = await instanceRepository.save({
432
531
  ...instance,
433
532
  completedAt: null,
434
533
  formData,
534
+ formDataOptionSnapshot: preparation?.formDataOptionSnapshot ??
535
+ // Without a resolver every remaining dynamic value was rejected
536
+ // above, so only cleared fields can reach here — drop their
537
+ // snapshots instead of keeping a label for a value that is gone.
538
+ readSnapshotForPresentValues(resubmitSchema, formData, instance.formDataOptionSnapshot),
435
539
  state: workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING,
436
540
  title: input.title?.trim() || instance.title,
437
541
  });
@@ -454,7 +558,13 @@ let WorkflowEngineService = class WorkflowEngineService {
454
558
  taskId: null,
455
559
  }));
456
560
  await this.processRunningInstance(manager, runtimeInstance);
457
- return runtimeInstance;
561
+ const refreshedInstance = await instanceRepository.findOne({
562
+ where: { id: instance.id },
563
+ });
564
+ if (!refreshedInstance) {
565
+ throw new common_1.NotFoundException(`Approval instance ${instance.id} was not found after resubmit`);
566
+ }
567
+ return refreshedInstance;
458
568
  });
459
569
  }
460
570
  async requestAdhocCountersign({ comment, requestedByMemberId, target, taskId, }) {
@@ -1344,6 +1454,7 @@ let WorkflowEngineService = class WorkflowEngineService {
1344
1454
  async listLaunchableApprovalTemplates(memberId) {
1345
1455
  const templates = await this.approvalTemplateRepository.find({
1346
1456
  order: { updatedAt: 'DESC' },
1457
+ where: { isActive: true },
1347
1458
  });
1348
1459
  const currentVersionIds = templates
1349
1460
  .map((template) => template.currentVersionId)
@@ -1895,6 +2006,10 @@ let WorkflowEngineService = class WorkflowEngineService {
1895
2006
  const candidates = await this.resolveRuntimeTaskCandidates(manager, instance, node);
1896
2007
  const primaryCandidate = candidates[0];
1897
2008
  const now = new Date();
2009
+ const slaDueAt = await this.slaScheduleService.resolveTaskSlaDueAt({
2010
+ node,
2011
+ now,
2012
+ });
1898
2013
  const task = await taskRepository.save(taskRepository.create({
1899
2014
  assigneeMemberId: candidates.length === 1 ? primaryCandidate.memberId : null,
1900
2015
  assignmentType: candidates.length === 1
@@ -1908,7 +2023,7 @@ let WorkflowEngineService = class WorkflowEngineService {
1908
2023
  nodeId: node.id,
1909
2024
  openedAt: null,
1910
2025
  originalAssigneeMemberId: candidates.length === 1 ? primaryCandidate.originalMemberId : null,
1911
- slaDueAt: (0, notification_service_1.calculateTaskSlaDueAt)({ node, now }),
2026
+ slaDueAt,
1912
2027
  status: workflow_engine_enums_1.TaskStatusEnum.PENDING,
1913
2028
  tokenId: token.id,
1914
2029
  }));
@@ -2006,7 +2121,7 @@ let WorkflowEngineService = class WorkflowEngineService {
2006
2121
  }
2007
2122
  return nextCandidates;
2008
2123
  }
2009
- async transferTask(manager, instance, task, decision, signature, transferToMemberId, decisionComment, decidedAt, actorCandidate) {
2124
+ async transferTask(manager, instance, task, decision, signature, transferToMemberId, decisionComment, decidedAt, actorCandidate, transferReason) {
2010
2125
  if (!transferToMemberId) {
2011
2126
  throw new common_1.BadRequestException('Transfer target member is required');
2012
2127
  }
@@ -2021,7 +2136,7 @@ let WorkflowEngineService = class WorkflowEngineService {
2021
2136
  ...readDelegationSteps(task.delegationChain),
2022
2137
  {
2023
2138
  from: transferringMemberId,
2024
- reason: 'MANUAL_TRANSFER',
2139
+ reason: transferReason,
2025
2140
  ruleId: null,
2026
2141
  to: transferToMemberId,
2027
2142
  },
@@ -2386,11 +2501,20 @@ let WorkflowEngineService = class WorkflowEngineService {
2386
2501
  if (action.type === 'SET_FORM_FIELD') {
2387
2502
  const value = this.conditionService.evaluateValue(action.value, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.action.value`);
2388
2503
  const updatedFormData = writeValueAtPath(instance.formData, action.fieldPath, value);
2504
+ // A service task runs inside this transaction, so it cannot call a host
2505
+ // provider to re-resolve the label. Rather than leave the previous
2506
+ // option snapshot describing a value that no longer exists, drop it: the
2507
+ // history view then shows the raw value instead of a wrong label.
2508
+ const staleSnapshotFieldKey = readDataSourceFieldKeyAtPath(instance.formDefinitionSnapshot, action.fieldPath);
2509
+ const updatedOptionSnapshot = staleSnapshotFieldKey
2510
+ ? readSnapshotWithoutField(instance.formDataOptionSnapshot, staleSnapshotFieldKey)
2511
+ : instance.formDataOptionSnapshot;
2389
2512
  const updatedInstance = await manager
2390
2513
  .getRepository(approval_instance_entity_1.ApprovalInstanceEntity)
2391
2514
  .save({
2392
2515
  ...instance,
2393
2516
  formData: updatedFormData,
2517
+ formDataOptionSnapshot: updatedOptionSnapshot,
2394
2518
  });
2395
2519
  Object.assign(instance, updatedInstance);
2396
2520
  await activityRepository.save(activityRepository.create({
@@ -2400,6 +2524,7 @@ let WorkflowEngineService = class WorkflowEngineService {
2400
2524
  nodeId: node.id,
2401
2525
  payload: {
2402
2526
  action: 'SET_FORM_FIELD',
2527
+ clearedOptionSnapshot: staleSnapshotFieldKey ?? null,
2403
2528
  fieldPath: action.fieldPath,
2404
2529
  tokenId: token.id,
2405
2530
  },
@@ -2715,8 +2840,10 @@ exports.WorkflowEngineService = WorkflowEngineService = tslib_1.__decorate([
2715
2840
  tslib_1.__param(8, (0, typeorm_1.InjectRepository)(approval_template_entity_1.ApprovalTemplateEntity)),
2716
2841
  tslib_1.__param(9, (0, typeorm_1.InjectRepository)(approval_template_version_entity_1.ApprovalTemplateVersionEntity)),
2717
2842
  tslib_1.__param(10, (0, typeorm_1.InjectRepository)(form_definition_version_entity_1.FormDefinitionVersionEntity)),
2718
- tslib_1.__param(16, (0, common_1.Optional)()),
2719
- tslib_1.__param(16, (0, common_1.Inject)(workflow_service_task_dispatcher_token_1.BPM_WORKFLOW_SERVICE_TASK_DISPATCHER)),
2843
+ tslib_1.__param(17, (0, common_1.Optional)()),
2844
+ tslib_1.__param(17, (0, common_1.Inject)(workflow_service_task_dispatcher_token_1.BPM_WORKFLOW_SERVICE_TASK_DISPATCHER)),
2845
+ tslib_1.__param(18, (0, common_1.Optional)()),
2846
+ tslib_1.__param(18, (0, common_1.Inject)(form_data_source_types_1.BPM_FORM_DATA_SOURCE_VALUE_RESOLVER)),
2720
2847
  tslib_1.__metadata("design:paramtypes", [typeorm_2.Repository,
2721
2848
  typeorm_2.Repository,
2722
2849
  typeorm_2.Repository,
@@ -2732,7 +2859,8 @@ exports.WorkflowEngineService = WorkflowEngineService = tslib_1.__decorate([
2732
2859
  condition_service_1.ConditionService,
2733
2860
  delegation_service_1.DelegationService,
2734
2861
  notification_service_1.NotificationService,
2735
- signature_service_1.SignatureService, Object])
2862
+ signature_service_1.SignatureService,
2863
+ sla_schedule_service_1.BPMSlaScheduleService, Object, Object])
2736
2864
  ], WorkflowEngineService);
2737
2865
  function parseJsonObject(value, label) {
2738
2866
  try {
@@ -2755,6 +2883,98 @@ function readDefaultInitiatorMetadataSnapshot(memberId) {
2755
2883
  memberId,
2756
2884
  };
2757
2885
  }
2886
+ function createWorkflowAuthContext(memberId) {
2887
+ return {
2888
+ memberId,
2889
+ metadata: {},
2890
+ permissions: [],
2891
+ roles: [],
2892
+ };
2893
+ }
2894
+ function isSameApprovalInstanceRevision(left, right) {
2895
+ return (readApprovalInstanceRevision(left) === readApprovalInstanceRevision(right) &&
2896
+ JSON.stringify(left.formData) === JSON.stringify(right.formData));
2897
+ }
2898
+ /**
2899
+ * Returns the field key a service-task `fieldPath` writes into when that field
2900
+ * is DataSource-backed, else `null`. Only the first path segment can name a
2901
+ * form field; deeper segments address inside its value.
2902
+ */
2903
+ /**
2904
+ * Reads defensively: a service task must keep running even when the instance
2905
+ * carries a snapshot this version cannot parse, so an unreadable schema simply
2906
+ * means "no snapshot to invalidate" rather than a failed workflow step.
2907
+ */
2908
+ function readDataSourceFieldKeyAtPath(snapshot, fieldPath) {
2909
+ const schema = snapshot?.schema;
2910
+ // Deliberately looser than `isFormDefinitionSchema`: one malformed sibling
2911
+ // entry must not stop a valid dynamic field from having its stale snapshot
2912
+ // pruned. Each entry is record-guarded below instead.
2913
+ if (!isRecord(schema) || !Array.isArray(schema.fields)) {
2914
+ return null;
2915
+ }
2916
+ // Reuse the writer's normalization so an optional `form.`/`formData.` prefix
2917
+ // resolves to the same field the value is written into.
2918
+ const [fieldKey] = normalizeFormFieldPath(fieldPath);
2919
+ if (!fieldKey) {
2920
+ return null;
2921
+ }
2922
+ // A corrupt snapshot can hold non-object entries: read them defensively
2923
+ // rather than letting a service task throw.
2924
+ const field = schema.fields.find((candidate) => isRecord(candidate) && candidate.fieldKey === fieldKey);
2925
+ return field && (0, form_1.isFormDataSourceFieldDefinition)(field) ? field.fieldKey : null;
2926
+ }
2927
+ function readSnapshotWithoutField(snapshots, fieldKey) {
2928
+ return Object.fromEntries(Object.entries(snapshots ?? {}).filter(([key]) => key !== fieldKey));
2929
+ }
2930
+ /**
2931
+ * Keeps only the snapshots whose field still carries a value, so clearing a
2932
+ * dynamic option also removes the label recorded for it.
2933
+ */
2934
+ function readSnapshotForPresentValues(schema, formData, snapshots) {
2935
+ return Object.fromEntries(Object.entries(snapshots ?? {}).filter(([fieldKey]) => schema.fields.some((field) => isRecord(field) &&
2936
+ field.fieldKey === fieldKey &&
2937
+ (0, form_1.isFormDataSourceFieldDefinition)(field) &&
2938
+ hasPresentDynamicValue(field, formData[fieldKey]))));
2939
+ }
2940
+ /**
2941
+ * Only a value the field's own selection mode can represent counts as cleared:
2942
+ * an empty array clears a `multiple` field, but on a `single` field it is an
2943
+ * illegal value that must still reach validation rather than silently drop the
2944
+ * option snapshot.
2945
+ */
2946
+ function hasPresentDynamicValue(field, value) {
2947
+ if (typeof value === 'undefined' || value === null) {
2948
+ return false;
2949
+ }
2950
+ // Only a shape the field's mode can actually hold counts as cleared: `''`
2951
+ // clears a single field, `[]` clears a multiple one. The other combinations
2952
+ // are illegal values that must still reach validation instead of silently
2953
+ // dropping the option snapshot.
2954
+ return (0, form_1.readFormFieldSelectionMode)(field) === 'multiple'
2955
+ ? !Array.isArray(value) || value.length > 0
2956
+ : value !== '';
2957
+ }
2958
+ /**
2959
+ * Hosts without a DataSource registry keep working for static forms, but a
2960
+ * dynamic value they cannot re-resolve must never reach the database: nothing
2961
+ * downstream could tell whether the browser made the value up.
2962
+ */
2963
+ function assertNoUnresolvableDynamicValues(schema, formData) {
2964
+ // A malformed snapshot can hold non-object entries; skip them rather than
2965
+ // throwing a TypeError out of a submit or resubmit.
2966
+ const hasDynamicValue = schema.fields.some((field) => isRecord(field) &&
2967
+ (0, form_1.isFormDataSourceFieldDefinition)(field) &&
2968
+ hasPresentDynamicValue(field, formData[field.fieldKey]));
2969
+ if (hasDynamicValue) {
2970
+ throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.DATA_SOURCE_MISSING);
2971
+ }
2972
+ }
2973
+ function readApprovalInstanceRevision(instance) {
2974
+ return instance.updatedAt instanceof Date
2975
+ ? instance.updatedAt.toISOString()
2976
+ : String(instance.updatedAt ?? '');
2977
+ }
2758
2978
  function parseWorkflowDefinition(value) {
2759
2979
  try {
2760
2980
  const parsedValue = JSON.parse(value);
@@ -2916,6 +3136,11 @@ function readDefaultReturnTargetNodeId(workflow, currentNode) {
2916
3136
  }
2917
3137
  return previousNodeId;
2918
3138
  }
3139
+ function isReturnCommentRequired(workflow, nodeId) {
3140
+ const node = readWorkflowNodeOrThrow(workflow, nodeId);
3141
+ return (node.type === 'userTask' &&
3142
+ node.data.returnBehavior.requireComment === true);
3143
+ }
2919
3144
  function readReturnResubmitStrategy(task, instance) {
2920
3145
  const node = readWorkflowNodeOrThrow(instance.workflowSnapshot, task.nodeId);
2921
3146
  if (node.type !== 'userTask') {
@@ -3411,7 +3636,12 @@ function readFormDefinitionSnapshotSchema(snapshot) {
3411
3636
  throw new common_1.BadRequestException('Approval instance form schema is invalid');
3412
3637
  }
3413
3638
  function isFormDefinitionSchema(value) {
3414
- return (isRecord(value) && value.schemaVersion === 1 && Array.isArray(value.fields));
3639
+ return (isRecord(value) &&
3640
+ value.schemaVersion === 1 &&
3641
+ Array.isArray(value.fields) &&
3642
+ // Every reader below dereferences `field.fieldKey`; requiring records here
3643
+ // turns a corrupt snapshot into a controlled 400 instead of a TypeError.
3644
+ value.fields.every((field) => isRecord(field)));
3415
3645
  }
3416
3646
  function isSubmittedFormFieldVisible(field, fields, values) {
3417
3647
  return field.visibleWhen