@rytass/bpm-core-nestjs-module 0.21.0 → 0.21.2

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 (45) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +105 -1
  3. package/package.json +2 -2
  4. package/src/lib/attachment/attachment.service.js +2 -10
  5. package/src/lib/attachment/attachment.service.js.map +1 -1
  6. package/src/lib/attachment/file-upload-value-ids.d.ts +8 -0
  7. package/src/lib/attachment/file-upload-value-ids.js +15 -0
  8. package/src/lib/attachment/file-upload-value-ids.js.map +1 -0
  9. package/src/lib/bpm/bpm-root-options.d.ts +18 -0
  10. package/src/lib/bpm/bpm-root-options.js.map +1 -1
  11. package/src/lib/bpm/bpm-root.module.d.ts +11 -0
  12. package/src/lib/bpm/bpm-root.module.js +9 -4
  13. package/src/lib/bpm/bpm-root.module.js.map +1 -1
  14. package/src/lib/form-data-source/form-data-source.module.d.ts +8 -0
  15. package/src/lib/form-data-source/form-data-source.module.js +1 -1
  16. package/src/lib/form-data-source/form-data-source.module.js.map +1 -1
  17. package/src/lib/form-data-source/form-data-source.service.js +1 -1
  18. package/src/lib/form-data-source/form-data-source.service.js.map +1 -1
  19. package/src/lib/notification/notification.service.js +6 -0
  20. package/src/lib/notification/notification.service.js.map +1 -1
  21. package/src/lib/organization/organization-time-zone.d.ts +4 -2
  22. package/src/lib/organization/organization-time-zone.js +3 -6
  23. package/src/lib/organization/organization-time-zone.js.map +1 -1
  24. package/src/lib/template/template.service.js +51 -5
  25. package/src/lib/template/template.service.js.map +1 -1
  26. package/src/lib/template/workflow-definition.validator.js +84 -2
  27. package/src/lib/template/workflow-definition.validator.js.map +1 -1
  28. package/src/lib/workflow-engine/index.d.ts +1 -0
  29. package/src/lib/workflow-engine/index.js +1 -0
  30. package/src/lib/workflow-engine/index.js.map +1 -1
  31. package/src/lib/workflow-engine/initiator-custom-fields-resolver.token.d.ts +108 -0
  32. package/src/lib/workflow-engine/initiator-custom-fields-resolver.token.js +9 -0
  33. package/src/lib/workflow-engine/initiator-custom-fields-resolver.token.js.map +1 -0
  34. package/src/lib/workflow-engine/workflow-engine.enums.d.ts +1 -0
  35. package/src/lib/workflow-engine/workflow-engine.enums.js +1 -0
  36. package/src/lib/workflow-engine/workflow-engine.enums.js.map +1 -1
  37. package/src/lib/workflow-engine/workflow-engine.module.d.ts +2 -0
  38. package/src/lib/workflow-engine/workflow-engine.module.js +9 -3
  39. package/src/lib/workflow-engine/workflow-engine.module.js.map +1 -1
  40. package/src/lib/workflow-engine/workflow-engine.queries.d.ts +1 -1
  41. package/src/lib/workflow-engine/workflow-engine.queries.js +4 -3
  42. package/src/lib/workflow-engine/workflow-engine.queries.js.map +1 -1
  43. package/src/lib/workflow-engine/workflow-engine.service.d.ts +131 -14
  44. package/src/lib/workflow-engine/workflow-engine.service.js +654 -88
  45. package/src/lib/workflow-engine/workflow-engine.service.js.map +1 -1
@@ -29,6 +29,7 @@ import { TaskCandidateEntity } from './task-candidate.entity';
29
29
  import { TaskEntity } from './task.entity';
30
30
  import { ApprovalInstanceListViewEnum, ApprovalInstanceStateEnum, MemberTaskRoleEnum, TaskCandidateStatusEnum, TaskReassignmentErrorCodeEnum, TaskReassignmentOutcomeEnum } from './workflow-engine.enums';
31
31
  import { WorkflowDryRunResultObject } from './workflow-dry-run.object';
32
+ import { BPMInitiatorCustomFieldsResolver } from './initiator-custom-fields-resolver.token';
32
33
  import { BPMWorkflowServiceTaskDispatcher } from './workflow-service-task-dispatcher.token';
33
34
  import { WorkflowTokenEntity } from './workflow-token.entity';
34
35
  import { BPMRootRuntimeOptions } from '../bpm/bpm-root-options';
@@ -180,8 +181,9 @@ export declare class WorkflowEngineService {
180
181
  private readonly logger;
181
182
  private readonly approvalInstanceFieldFilterCompiler;
182
183
  private readonly approvalInstanceFilterableFieldPolicy;
183
- constructor(approvalInstanceRepository: Repository<ApprovalInstanceEntity>, workflowTokenRepository: Repository<WorkflowTokenEntity>, taskRepository: Repository<TaskEntity>, taskCandidateRepository: Repository<TaskCandidateEntity>, taskDecisionRepository: Repository<TaskDecisionEntity>, notificationRepository: Repository<NotificationEntity>, activityLogRepository: Repository<ActivityLogEntity>, adhocDirectiveRepository: Repository<AdhocDirectiveEntity>, approvalTemplateRepository: Repository<ApprovalTemplateEntity>, approvalTemplateVersionRepository: Repository<ApprovalTemplateVersionEntity>, formDefinitionVersionRepository: Repository<FormDefinitionVersionEntity>, attachmentService: AttachmentService, conditionService: ConditionService, delegationService: DelegationService, notificationService: NotificationService, signatureService: SignatureService, slaScheduleService: BPMSlaScheduleService, injectedServiceTaskDispatcher?: BPMWorkflowServiceTaskDispatcher, formDataSourceValueResolver?: BPMFormDataSourceValueResolver | undefined, rootOptions?: BPMRootRuntimeOptions, webhookDeliveryService?: WorkflowWebhookDeliveryService | undefined);
184
+ constructor(approvalInstanceRepository: Repository<ApprovalInstanceEntity>, workflowTokenRepository: Repository<WorkflowTokenEntity>, taskRepository: Repository<TaskEntity>, taskCandidateRepository: Repository<TaskCandidateEntity>, taskDecisionRepository: Repository<TaskDecisionEntity>, notificationRepository: Repository<NotificationEntity>, activityLogRepository: Repository<ActivityLogEntity>, adhocDirectiveRepository: Repository<AdhocDirectiveEntity>, approvalTemplateRepository: Repository<ApprovalTemplateEntity>, approvalTemplateVersionRepository: Repository<ApprovalTemplateVersionEntity>, formDefinitionVersionRepository: Repository<FormDefinitionVersionEntity>, attachmentService: AttachmentService, conditionService: ConditionService, delegationService: DelegationService, notificationService: NotificationService, signatureService: SignatureService, slaScheduleService: BPMSlaScheduleService, injectedServiceTaskDispatcher?: BPMWorkflowServiceTaskDispatcher, formDataSourceValueResolver?: BPMFormDataSourceValueResolver | undefined, rootOptions?: BPMRootRuntimeOptions, webhookDeliveryService?: WorkflowWebhookDeliveryService | undefined, injectedInitiatorCustomFieldsResolver?: BPMInitiatorCustomFieldsResolver);
184
185
  private readonly serviceTaskDispatcher;
186
+ private readonly initiatorCustomFieldsResolver;
185
187
  /** Webhooks each open engine transaction will send once it has committed. */
186
188
  private readonly deferredWebhookDispatches;
187
189
  private readonly organizationTimeZone;
@@ -362,7 +364,12 @@ export declare class WorkflowEngineService {
362
364
  readonly totalInstanceCount: number;
363
365
  }>;
364
366
  private readDashboardOpenTasks;
365
- listLaunchableApprovalTemplates(memberId: string): Promise<readonly ApprovalTemplateEntity[]>;
367
+ /**
368
+ * The active templates `memberId` may start now. `authContext` is handed to
369
+ * the host's initiator `customFields` resolver; a host calling this without
370
+ * one gets a bare context for the member, as a submission does.
371
+ */
372
+ listLaunchableApprovalTemplates(memberId: string, authContext?: BPMAuthContext): Promise<readonly ApprovalTemplateEntity[]>;
366
373
  listWorkflowTokens(instanceId: string, scope?: WorkflowReadScope): Promise<readonly WorkflowTokenEntity[]>;
367
374
  listTasks(instanceId: string, scope?: WorkflowReadScope): Promise<readonly TaskEntity[]>;
368
375
  listInboxTasks(assigneeMemberId: string): Promise<readonly TaskEntity[]>;
@@ -394,6 +401,54 @@ export declare class WorkflowEngineService {
394
401
  */
395
402
  private readTaskNodeSummaries;
396
403
  private readDefaultInitiatorMetadataSnapshot;
404
+ /**
405
+ * The initiator snapshot the engine builds itself: what it knows of the
406
+ * initiator, with `customFields` filled by the host's resolver when there is
407
+ * one. Without a resolver this is exactly the default snapshot
408
+ * (`customFields` is `{}`). A submission (`submission` set) stores it and
409
+ * judges the initiator policy with it; the launchable template list
410
+ * (`submission: null`) only judges the policies with it, so both see the same
411
+ * `customFields` for the same member.
412
+ *
413
+ * Only `customFields` comes from the host. The other keys are the engine's
414
+ * and are written back after the resolver returns, so nothing it does can
415
+ * change who the initiator is. The resolver gets copies of the snapshot and
416
+ * of the form data for the same reason; `authContext` is the caller's own
417
+ * object, as every other host hook receives it.
418
+ *
419
+ * The two callers treat a resolver failure (an error, a bad value, the
420
+ * timeout) differently, on purpose:
421
+ *
422
+ * - A submission fails, and no case is created. Its snapshot is stored and
423
+ * routes the case from then on, so carrying on with `{}` would start a
424
+ * case on data the host never gave. It runs before the engine
425
+ * transaction, so a failure leaves nothing behind and a slow lookup holds
426
+ * no lock.
427
+ * - The list carries on with `customFields: {}` and logs a warning: one
428
+ * failing lookup must not take the whole list away. That fails closed: a
429
+ * policy that reads `initiator.customFields` then throws on the missing key
430
+ * and the list's per-template catch leaves that template out, while a
431
+ * template whose policy does not read it is listed as usual. Submitting is
432
+ * still judged against the resolver's real answer.
433
+ */
434
+ private buildInitiatorMetadataSnapshot;
435
+ /**
436
+ * Calls the host's initiator `customFields` resolver within
437
+ * {@link INITIATOR_CUSTOM_FIELDS_RESOLVER_TIMEOUT_MS} and returns what it
438
+ * gave as the object the snapshot keeps.
439
+ *
440
+ * Every failure becomes an `InternalServerErrorException` with a message
441
+ * meant for the member, and the cause goes to the log only, so nothing of
442
+ * the host's lookup reaches the client: running out of time, an error that
443
+ * is not an `HttpException`, and a value that is not a plain JSON object. An
444
+ * `HttpException` the resolver throws is passed on as it is, because the
445
+ * host chose that answer for the member.
446
+ *
447
+ * A resolver that settles after the time has run out is not waited for; what
448
+ * it settles with is logged and dropped, so a late rejection is never left
449
+ * unhandled.
450
+ */
451
+ private resolveInitiatorCustomFields;
397
452
  listActivityLogs(instanceId: string, scope?: WorkflowReadScope): Promise<readonly ActivityLogEntity[]>;
398
453
  private assertTaskReadable;
399
454
  private isApprovalInstanceReadableByMember;
@@ -424,10 +479,11 @@ export declare class WorkflowEngineService {
424
479
  * Re-resolves the delegation of a candidate whose turn has come. When the
425
480
  * delegate already holds another candidate row on the task, the stored values
426
481
  * are kept: (task_id, member_id) is unique, and that member signs from the
427
- * row they already have. On a node that excludes the initiator the stored
428
- * values are kept as well when the new delegation path would run through the
429
- * initiator or the submitter: creation already filtered such paths out, and a
430
- * delegation added while the candidate waited must not bring them back.
482
+ * row they already have. On a node that keeps members out (the initiator and
483
+ * submitter, or the approvers of earlier stages) the stored values are kept
484
+ * as well when the new delegation path would run through one of them:
485
+ * creation already filtered such paths out, and a delegation added while the
486
+ * candidate waited must not bring them back.
431
487
  */
432
488
  private resolveActivationDelegation;
433
489
  private markTaskCandidateDecision;
@@ -530,12 +586,52 @@ export declare class WorkflowEngineService {
530
586
  */
531
587
  private notifySeatReplaced;
532
588
  /**
533
- * Resolves who gets the node's task. `null` means the node asked to be
534
- * skipped (`excludeInitiator.whenEmpty = SKIP_NODE`) because the initiator
535
- * was the only approver — the sole caller creates no task and advances the
536
- * token instead.
589
+ * Resolves who gets the node's task. A {@link SkippedRuntimeTask} means the
590
+ * node asked to be skipped (`excludeInitiator.whenEmpty` or
591
+ * `excludeNodeActors.whenEmpty` = SKIP_NODE) because everyone left was kept
592
+ * out: the sole caller creates no task and advances the token instead.
593
+ *
594
+ * With both exclusions set the initiator rule runs first. When it alone
595
+ * leaves nobody, `excludeInitiator.whenEmpty` decides; when it leaves
596
+ * somebody and the earlier-stage rule then removes them all,
597
+ * `excludeNodeActors.whenEmpty` decides.
537
598
  */
538
599
  private resolveRuntimeTaskCandidates;
600
+ /**
601
+ * Everyone `node` keeps out of its approvers, per rule: the initiator and
602
+ * the submitter for `excludeInitiator`, and everyone who actually approved
603
+ * one of the listed earlier stages for `excludeNodeActors`. Read through
604
+ * `manager` so an approval written earlier in this transaction counts.
605
+ */
606
+ private readNodeExclusions;
607
+ /**
608
+ * The people who approved any of `nodeIds`, each listed node read on its own
609
+ * with the `NODE_ACTOR` rules ({@link readNodeActorMemberIds}) and the
610
+ * results combined. A source reads only the most recent of its listed nodes;
611
+ * an exclusion must cover every listed node, so it asks one node at a time.
612
+ *
613
+ * There is no round boundary here: a node's newest batch is what counts. The
614
+ * publish lint makes that the right batch, since every listed node runs and
615
+ * is signed by a person on every path to this node (a return to the start
616
+ * runs it again; a return to a stage below it leaves its earlier approval
617
+ * standing, and the exclusion with it).
618
+ */
619
+ private readExcludedNodeActorMemberIds;
620
+ /**
621
+ * Whether `targetMemberId` may not take a seat on `node` under
622
+ * `excludeNodeActors`, checked the way task creation checks a candidate: the
623
+ * target is one of the excluded members, or one end of a delegation between
624
+ * the target and an excluded member is on the path. A delegation is read in
625
+ * both directions, since the target may be the delegate of an excluded
626
+ * member, or delegate an excluded member's seat onward.
627
+ */
628
+ private isExcludedNodeActorTarget;
629
+ /**
630
+ * The principals the approvals stand for: a delegate who approved counts as
631
+ * the member who delegated to them, read from the candidate row each approval
632
+ * was signed on (see {@link readRuleDelegatorMemberIds}).
633
+ */
634
+ private readApprovalPrincipalMemberIds;
539
635
  private resolveInitiatorManagerReplacements;
540
636
  private buildDelegationContext;
541
637
  private delegateResolvedCandidates;
@@ -600,6 +696,12 @@ export declare class WorkflowEngineService {
600
696
  * tasks and approvals the SLA timeout made do not count.
601
697
  */
602
698
  private readNodeActorMemberIds;
699
+ /**
700
+ * The real approvals (earliest first) of the most recent batch among
701
+ * `nodeIds`: the rows {@link readNodeActorMemberIds} reads the approvers from.
702
+ * Kept as decisions so the exclusion can also reach each decision's task.
703
+ */
704
+ private readNodeActorApprovals;
603
705
  private resolvePositionCandidates;
604
706
  /**
605
707
  * The members who hold the position today (organisation time zone) within
@@ -609,12 +711,27 @@ export declare class WorkflowEngineService {
609
711
  private readActivePositionMemberIds;
610
712
  private resolveInitiatorManagerCandidates;
611
713
  /**
612
- * Walks up the initiator's manager chain, nearest manager first, until a
613
- * manager whose position level reaches `minPositionLevel` is added. The
614
- * list is returned nearest manager first. Members in `skippedMemberIds` are
615
- * walked past without joining the chain or counting as the stopping point.
714
+ * Walks up a manager chain, nearest manager first, until a manager whose
715
+ * position level reaches `minPositionLevel` is added. The chain starts at
716
+ * the initiator unless `startFrom` names someone else, and the start person
717
+ * is never on it: the walk begins at their direct manager. The list is
718
+ * returned nearest manager first. Members in `skippedMemberIds` are walked
719
+ * past without joining the chain or counting as the stopping point, and so
720
+ * are managers below `startPositionLevel`, until the first one at or above
721
+ * it starts the chain.
616
722
  */
617
723
  private resolveOrgManagerChainCandidates;
724
+ /**
725
+ * Whose manager an `ORG_MANAGER_CHAIN` starts from. `memberId` is `null`
726
+ * when the start names nobody (an empty form field, an earlier stage nobody
727
+ * approved), with `missingReason` saying why; `description` is the lead-in
728
+ * of the "no manager reaches the level" reason, empty for the initiator so
729
+ * a chain without a start keeps today's wording.
730
+ *
731
+ * A start that picks more than one person is refused rather than guessed,
732
+ * the same as a manager rule that names several managers at one priority.
733
+ */
734
+ private resolveOrgManagerChainStart;
618
735
  /** Highest position level over the member's active memberships, or `null`. */
619
736
  private readMemberPositionLevel;
620
737
  /**