@rytass/bpm-core-nestjs-module 0.17.7 → 0.17.9
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 +31 -0
- package/README.md +13 -0
- package/package.json +2 -2
- package/src/lib/identity/identity.service.d.ts +6 -0
- package/src/lib/identity/identity.service.js +12 -0
- package/src/lib/identity/identity.service.js.map +1 -1
- package/src/lib/identity/member-base.adapter.d.ts +8 -0
- package/src/lib/identity/member-base.adapter.js +8 -2
- package/src/lib/identity/member-base.adapter.js.map +1 -1
- package/src/lib/identity/member-resolver.interface.d.ts +9 -0
- package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.d.ts +7 -0
- package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.js +7 -0
- package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.js.map +1 -1
- package/src/lib/workflow-engine/task-reassignment.object.d.ts +11 -0
- package/src/lib/workflow-engine/task-reassignment.object.js +27 -1
- package/src/lib/workflow-engine/task-reassignment.object.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.queries.d.ts +12 -2
- package/src/lib/workflow-engine/workflow-engine.queries.js +45 -2
- package/src/lib/workflow-engine/workflow-engine.queries.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.service.d.ts +32 -0
- package/src/lib/workflow-engine/workflow-engine.service.js +105 -0
- package/src/lib/workflow-engine/workflow-engine.service.js.map +1 -1
|
@@ -113,6 +113,15 @@ class TaskReassignmentRejectedError extends Error {
|
|
|
113
113
|
this.errorCode = errorCode;
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
+
/** The administrator screens are in Traditional Chinese: names sort by stroke count. */
|
|
117
|
+
const INACTIVE_MEMBER_NAME_LOCALE = 'zh-Hant-TW';
|
|
118
|
+
/**
|
|
119
|
+
* How many deactivated members have their open tasks counted at once. Each
|
|
120
|
+
* count is a few queries, so an unbounded fan-out would put one query in
|
|
121
|
+
* flight per member, and claim that many pool connections at the same time, on
|
|
122
|
+
* every load of the member screen.
|
|
123
|
+
*/
|
|
124
|
+
const INACTIVE_MEMBER_COUNT_CONCURRENCY = 5;
|
|
116
125
|
const WORKFLOW_READ_ALL_PERMISSIONS = new Set([
|
|
117
126
|
'bpm:*',
|
|
118
127
|
'bpm.workflow.read_all',
|
|
@@ -908,6 +917,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
908
917
|
instanceId: instance.id,
|
|
909
918
|
nodeId: null,
|
|
910
919
|
payload: {
|
|
920
|
+
comment: input.comment?.trim() || null,
|
|
911
921
|
resubmitStrategy: returnedContext?.resubmitStrategy ?? 'RESTART',
|
|
912
922
|
},
|
|
913
923
|
taskId: null,
|
|
@@ -3340,6 +3350,97 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
3340
3350
|
};
|
|
3341
3351
|
});
|
|
3342
3352
|
}
|
|
3353
|
+
/**
|
|
3354
|
+
* Deactivated members who still hold open tasks, so an administrator can find
|
|
3355
|
+
* them: a deactivated member no longer shows up in the member search.
|
|
3356
|
+
*
|
|
3357
|
+
* BPM cannot tell a deactivated member from an id it never knew, so the host
|
|
3358
|
+
* names them: `resolveInactiveMembers` gets every member id that holds an open
|
|
3359
|
+
* seat and returns the ones it knows but has deactivated. The count per member
|
|
3360
|
+
* is the {@link listMemberOpenTasks} count, and members left with none are
|
|
3361
|
+
* dropped.
|
|
3362
|
+
*/
|
|
3363
|
+
async listInactiveMembersWithOpenTasks(resolveInactiveMembers) {
|
|
3364
|
+
const holderIds = await this.readOpenSeatHolderMemberIds();
|
|
3365
|
+
if (!holderIds.length) {
|
|
3366
|
+
return [];
|
|
3367
|
+
}
|
|
3368
|
+
const holderIdSet = new Set(holderIds);
|
|
3369
|
+
// Only the ids it was asked about are counted, each once: a member the
|
|
3370
|
+
// host adds would only cost task queries before the count dropped it.
|
|
3371
|
+
const inactiveMembers = [
|
|
3372
|
+
...new Map((await resolveInactiveMembers(holderIds))
|
|
3373
|
+
.filter((member) => holderIdSet.has(member.memberId))
|
|
3374
|
+
.map((member) => [member.memberId, member])).values(),
|
|
3375
|
+
];
|
|
3376
|
+
const counted = await chunkArray(inactiveMembers, INACTIVE_MEMBER_COUNT_CONCURRENCY).reduce(async (previous, batch) => [
|
|
3377
|
+
...(await previous),
|
|
3378
|
+
...(await Promise.all(batch.map(async (member) => ({
|
|
3379
|
+
email: member.email,
|
|
3380
|
+
memberId: member.memberId,
|
|
3381
|
+
name: member.name,
|
|
3382
|
+
openTaskCount: (await this.readRunningMemberTasks(member.memberId))
|
|
3383
|
+
.length,
|
|
3384
|
+
})))),
|
|
3385
|
+
], Promise.resolve([]));
|
|
3386
|
+
return counted
|
|
3387
|
+
.filter((record) => record.openTaskCount > 0)
|
|
3388
|
+
.sort((left, right) => left.name.localeCompare(right.name, INACTIVE_MEMBER_NAME_LOCALE) ||
|
|
3389
|
+
// Namesakes are common; the id keeps their order stable.
|
|
3390
|
+
(left.memberId < right.memberId
|
|
3391
|
+
? -1
|
|
3392
|
+
: left.memberId > right.memberId
|
|
3393
|
+
? 1
|
|
3394
|
+
: 0));
|
|
3395
|
+
}
|
|
3396
|
+
/**
|
|
3397
|
+
* Every member id that may hold an open seat on a RUNNING instance: the
|
|
3398
|
+
* assignee of an open task, or an open candidate row on one. Still a
|
|
3399
|
+
* superset (a countersign voter who already decided stays the assignee);
|
|
3400
|
+
* callers count with {@link readRunningMemberTasks}, which drops those.
|
|
3401
|
+
*
|
|
3402
|
+
* One query that returns only the distinct holder ids, never a row per
|
|
3403
|
+
* task or seat: the member screen loads it every time.
|
|
3404
|
+
*/
|
|
3405
|
+
async readOpenSeatHolderMemberIds() {
|
|
3406
|
+
const taskTable = this.readQualifiedTableName(this.taskRepository);
|
|
3407
|
+
const candidateTable = this.readQualifiedTableName(this.taskCandidateRepository);
|
|
3408
|
+
const instanceTable = this.readQualifiedTableName(this.approvalInstanceRepository);
|
|
3409
|
+
const rows = await this.approvalInstanceRepository.query(`SELECT holder.member_id AS "memberId"
|
|
3410
|
+
FROM (
|
|
3411
|
+
SELECT task.assignee_member_id AS member_id
|
|
3412
|
+
FROM ${taskTable} AS task
|
|
3413
|
+
JOIN ${instanceTable} AS instance
|
|
3414
|
+
ON instance.id = task.instance_id
|
|
3415
|
+
WHERE task.status = ANY($1::text[])
|
|
3416
|
+
AND task.assignee_member_id IS NOT NULL
|
|
3417
|
+
AND instance.state = $2
|
|
3418
|
+
UNION
|
|
3419
|
+
SELECT candidate.member_id
|
|
3420
|
+
FROM ${candidateTable} AS candidate
|
|
3421
|
+
JOIN ${taskTable} AS task ON task.id = candidate.task_id
|
|
3422
|
+
JOIN ${instanceTable} AS instance
|
|
3423
|
+
ON instance.id = task.instance_id
|
|
3424
|
+
WHERE candidate.status = ANY($3::text[])
|
|
3425
|
+
AND task.status = ANY($1::text[])
|
|
3426
|
+
AND instance.state = $2
|
|
3427
|
+
) AS holder`, [
|
|
3428
|
+
[workflow_engine_enums_1.TaskStatusEnum.PENDING, workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS],
|
|
3429
|
+
workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING,
|
|
3430
|
+
[...OPEN_SEAT_STATUSES],
|
|
3431
|
+
]);
|
|
3432
|
+
// Sorted here, not in SQL: the default JS order (UTF-16 code units),
|
|
3433
|
+
// whatever the DB collation.
|
|
3434
|
+
return rows.map((row) => row.memberId).sort();
|
|
3435
|
+
}
|
|
3436
|
+
/** `schema.table` of a repository's entity, escaped for raw SQL. */
|
|
3437
|
+
readQualifiedTableName(repository) {
|
|
3438
|
+
const { driver } = this.approvalInstanceRepository.manager.connection;
|
|
3439
|
+
return [repository.metadata.schema, repository.metadata.tableName]
|
|
3440
|
+
.filter((part) => Boolean(part))
|
|
3441
|
+
.map((part) => driver.escape(part))
|
|
3442
|
+
.join('.');
|
|
3443
|
+
}
|
|
3343
3444
|
/**
|
|
3344
3445
|
* The open tasks `memberId` holds on RUNNING instances, with the instance
|
|
3345
3446
|
* each belongs to. The instance filter is applied here, not only in the query.
|
|
@@ -5081,6 +5182,10 @@ function capDecisionPolicyToCandidateCount(policy, candidateCount) {
|
|
|
5081
5182
|
? { ...policy, threshold: Math.max(candidateCount, 1) }
|
|
5082
5183
|
: policy;
|
|
5083
5184
|
}
|
|
5185
|
+
/** Splits `items` into consecutive runs of at most `size`. */
|
|
5186
|
+
function chunkArray(items, size) {
|
|
5187
|
+
return Array.from({ length: Math.ceil(items.length / size) }, (_, index) => items.slice(index * size, (index + 1) * size));
|
|
5188
|
+
}
|
|
5084
5189
|
function uniqueTasksById(tasks) {
|
|
5085
5190
|
const seen = new Set();
|
|
5086
5191
|
return tasks.filter((task) => {
|