@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.
- package/CHANGELOG.md +133 -0
- package/README.md +60 -1
- package/package.json +17 -3
- package/src/index.d.ts +2 -0
- package/src/index.js +2 -0
- package/src/index.js.map +1 -1
- package/src/lib/bpm/bpm-root.module.d.ts +33 -0
- package/src/lib/bpm/bpm-root.module.js +20 -0
- package/src/lib/bpm/bpm-root.module.js.map +1 -1
- package/src/lib/calendar/business-calendar.provider.d.ts +11 -0
- package/src/lib/calendar/business-calendar.provider.js +20 -0
- package/src/lib/calendar/business-calendar.provider.js.map +1 -0
- package/src/lib/calendar/business-calendar.token.d.ts +38 -0
- package/src/lib/calendar/business-calendar.token.js +5 -0
- package/src/lib/calendar/business-calendar.token.js.map +1 -0
- package/src/lib/calendar/calendar.module.d.ts +22 -0
- package/src/lib/calendar/calendar.module.js +33 -0
- package/src/lib/calendar/calendar.module.js.map +1 -0
- package/src/lib/calendar/index.d.ts +5 -0
- package/src/lib/calendar/index.js +9 -0
- package/src/lib/calendar/index.js.map +1 -0
- package/src/lib/calendar/sla-schedule.service.d.ts +33 -0
- package/src/lib/calendar/sla-schedule.service.js +94 -0
- package/src/lib/calendar/sla-schedule.service.js.map +1 -0
- package/src/lib/calendar/weekday-business-calendar.d.ts +15 -0
- package/src/lib/calendar/weekday-business-calendar.js +28 -0
- package/src/lib/calendar/weekday-business-calendar.js.map +1 -0
- package/src/lib/common/iso-duration.d.ts +11 -0
- package/src/lib/common/iso-duration.js +13 -4
- package/src/lib/common/iso-duration.js.map +1 -1
- package/src/lib/form/form-schema.validator.js +177 -10
- package/src/lib/form/form-schema.validator.js.map +1 -1
- package/src/lib/form/form.service.d.ts +19 -1
- package/src/lib/form/form.service.js +56 -6
- package/src/lib/form/form.service.js.map +1 -1
- package/src/lib/form-data-source/form-data-source-value-resolver.service.d.ts +16 -0
- package/src/lib/form-data-source/form-data-source-value-resolver.service.js +401 -0
- package/src/lib/form-data-source/form-data-source-value-resolver.service.js.map +1 -0
- package/src/lib/form-data-source/form-data-source.errors.d.ts +34 -0
- package/src/lib/form-data-source/form-data-source.errors.js +43 -0
- package/src/lib/form-data-source/form-data-source.errors.js.map +1 -0
- package/src/lib/form-data-source/form-data-source.module.d.ts +9 -0
- package/src/lib/form-data-source/form-data-source.module.js +55 -0
- package/src/lib/form-data-source/form-data-source.module.js.map +1 -0
- package/src/lib/form-data-source/form-data-source.queries.d.ts +57 -0
- package/src/lib/form-data-source/form-data-source.queries.js +293 -0
- package/src/lib/form-data-source/form-data-source.queries.js.map +1 -0
- package/src/lib/form-data-source/form-data-source.service.d.ts +72 -0
- package/src/lib/form-data-source/form-data-source.service.js +646 -0
- package/src/lib/form-data-source/form-data-source.service.js.map +1 -0
- package/src/lib/form-data-source/form-data-source.types.d.ts +88 -0
- package/src/lib/form-data-source/form-data-source.types.js +29 -0
- package/src/lib/form-data-source/form-data-source.types.js.map +1 -0
- package/src/lib/form-data-source/index.d.ts +5 -0
- package/src/lib/form-data-source/index.js +9 -0
- package/src/lib/form-data-source/index.js.map +1 -0
- package/src/lib/migrations/0000000018000-notification-archive.d.ts +6 -0
- package/src/lib/migrations/0000000018000-notification-archive.js +28 -0
- package/src/lib/migrations/0000000018000-notification-archive.js.map +1 -0
- package/src/lib/migrations/0000000019000-approval-template-activation.d.ts +6 -0
- package/src/lib/migrations/0000000019000-approval-template-activation.js +29 -0
- package/src/lib/migrations/0000000019000-approval-template-activation.js.map +1 -0
- package/src/lib/migrations/0000000020000-form-data-option-snapshots.d.ts +6 -0
- package/src/lib/migrations/0000000020000-form-data-option-snapshots.js +22 -0
- package/src/lib/migrations/0000000020000-form-data-option-snapshots.js.map +1 -0
- package/src/lib/migrations/index.d.ts +3 -0
- package/src/lib/migrations/index.js +9 -0
- package/src/lib/migrations/index.js.map +1 -1
- package/src/lib/notification/index.d.ts +3 -0
- package/src/lib/notification/index.js +3 -0
- package/src/lib/notification/index.js.map +1 -1
- package/src/lib/notification/notification-observer.token.d.ts +52 -0
- package/src/lib/notification/notification-observer.token.js +5 -0
- package/src/lib/notification/notification-observer.token.js.map +1 -0
- package/src/lib/notification/notification-options.d.ts +10 -0
- package/src/lib/notification/notification-options.js +19 -0
- package/src/lib/notification/notification-options.js.map +1 -1
- package/src/lib/notification/notification.entity.d.ts +11 -0
- package/src/lib/notification/notification.entity.js +17 -0
- package/src/lib/notification/notification.entity.js.map +1 -1
- package/src/lib/notification/notification.enums.d.ts +13 -0
- package/src/lib/notification/notification.enums.js +14 -1
- package/src/lib/notification/notification.enums.js.map +1 -1
- package/src/lib/notification/notification.mutations.d.ts +2 -0
- package/src/lib/notification/notification.mutations.js +28 -0
- package/src/lib/notification/notification.mutations.js.map +1 -1
- package/src/lib/notification/notification.queries.d.ts +2 -2
- package/src/lib/notification/notification.queries.js +12 -8
- package/src/lib/notification/notification.queries.js.map +1 -1
- package/src/lib/notification/notification.service.d.ts +42 -2
- package/src/lib/notification/notification.service.js +135 -4
- package/src/lib/notification/notification.service.js.map +1 -1
- package/src/lib/template/approval-template.entity.d.ts +13 -0
- package/src/lib/template/approval-template.entity.js +12 -1
- package/src/lib/template/approval-template.entity.js.map +1 -1
- package/src/lib/template/template.enums.d.ts +5 -0
- package/src/lib/template/template.enums.js +10 -1
- package/src/lib/template/template.enums.js.map +1 -1
- package/src/lib/template/template.mutations.d.ts +2 -0
- package/src/lib/template/template.mutations.js +20 -0
- package/src/lib/template/template.mutations.js.map +1 -1
- package/src/lib/template/template.queries.d.ts +3 -3
- package/src/lib/template/template.queries.js +14 -4
- package/src/lib/template/template.queries.js.map +1 -1
- package/src/lib/template/template.service.d.ts +7 -2
- package/src/lib/template/template.service.js +56 -11
- package/src/lib/template/template.service.js.map +1 -1
- package/src/lib/template/workflow-definition.validator.js +86 -1
- package/src/lib/template/workflow-definition.validator.js.map +1 -1
- package/src/lib/workflow-engine/approval-instance.entity.d.ts +3 -0
- package/src/lib/workflow-engine/approval-instance.entity.js +15 -0
- package/src/lib/workflow-engine/approval-instance.entity.js.map +1 -1
- package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.d.ts +5 -1
- package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.js +6 -2
- package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.js.map +1 -1
- package/src/lib/workflow-engine/dto/submit-approval-instance.input.d.ts +5 -1
- package/src/lib/workflow-engine/dto/submit-approval-instance.input.js +6 -2
- package/src/lib/workflow-engine/dto/submit-approval-instance.input.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.mutations.d.ts +3 -2
- package/src/lib/workflow-engine/workflow-engine.mutations.js +8 -6
- package/src/lib/workflow-engine/workflow-engine.mutations.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.service.d.ts +37 -4
- package/src/lib/workflow-engine/workflow-engine.service.js +245 -15
- package/src/lib/workflow-engine/workflow-engine.service.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.tokens.d.ts +2 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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(
|
|
2719
|
-
tslib_1.__param(
|
|
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,
|
|
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) &&
|
|
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
|