@rytass/bpm-core-nestjs-module 0.5.0 → 0.5.1
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 +19 -0
- package/package.json +1 -1
- package/src/index.js +18 -0
- package/src/index.js.map +1 -0
- package/src/lib/attachment/attachment-options.d.ts +44 -0
- package/src/lib/attachment/attachment-options.js +62 -0
- package/src/lib/attachment/attachment-options.js.map +1 -0
- package/src/lib/attachment/attachment-storage.provider.d.ts +4 -0
- package/src/lib/attachment/attachment-storage.provider.js +35 -0
- package/src/lib/attachment/attachment-storage.provider.js.map +1 -0
- package/src/lib/attachment/{attachment-storage.token.ts → attachment-storage.token.d.ts} +1 -3
- package/src/lib/attachment/attachment-storage.token.js +5 -0
- package/src/lib/attachment/attachment-storage.token.js.map +1 -0
- package/src/lib/attachment/attachment.controller.d.ts +8 -0
- package/src/lib/attachment/attachment.controller.js +38 -0
- package/src/lib/attachment/attachment.controller.js.map +1 -0
- package/src/lib/attachment/attachment.entity.d.ts +14 -0
- package/src/lib/attachment/attachment.entity.js +74 -0
- package/src/lib/attachment/attachment.entity.js.map +1 -0
- package/src/lib/attachment/attachment.module.d.ts +27 -0
- package/src/lib/attachment/attachment.module.js +103 -0
- package/src/lib/attachment/attachment.module.js.map +1 -0
- package/src/lib/attachment/attachment.mutations.d.ts +8 -0
- package/src/lib/attachment/attachment.mutations.js +35 -0
- package/src/lib/attachment/attachment.mutations.js.map +1 -0
- package/src/lib/attachment/attachment.queries.d.ts +9 -0
- package/src/lib/attachment/attachment.queries.js +78 -0
- package/src/lib/attachment/attachment.queries.js.map +1 -0
- package/src/lib/attachment/attachment.service.d.ts +49 -0
- package/src/lib/attachment/attachment.service.js +276 -0
- package/src/lib/attachment/attachment.service.js.map +1 -0
- package/src/lib/attachment/dto/upload-attachment.input.d.ts +11 -0
- package/src/lib/attachment/dto/upload-attachment.input.js +68 -0
- package/src/lib/attachment/dto/upload-attachment.input.js.map +1 -0
- package/src/lib/attachment/index.js +9 -0
- package/src/lib/attachment/index.js.map +1 -0
- package/src/lib/bpm/bpm-root.module.d.ts +155 -0
- package/src/lib/bpm/bpm-root.module.js +170 -0
- package/src/lib/bpm/bpm-root.module.js.map +1 -0
- package/src/lib/bpm/index.js +5 -0
- package/src/lib/bpm/index.js.map +1 -0
- package/src/lib/bpm-auth/bpm-auth-context.d.ts +24 -0
- package/src/lib/bpm-auth/bpm-auth-context.extractor.d.ts +4 -0
- package/src/lib/bpm-auth/bpm-auth-context.extractor.js +69 -0
- package/src/lib/bpm-auth/bpm-auth-context.extractor.js.map +1 -0
- package/src/lib/bpm-auth/bpm-auth-context.js +15 -0
- package/src/lib/bpm-auth/bpm-auth-context.js.map +1 -0
- package/src/lib/bpm-auth/bpm-auth.authorization.d.ts +12 -0
- package/src/lib/bpm-auth/bpm-auth.authorization.js +67 -0
- package/src/lib/bpm-auth/bpm-auth.authorization.js.map +1 -0
- package/src/lib/bpm-auth/bpm-auth.decorators.d.ts +3 -0
- package/src/lib/bpm-auth/bpm-auth.decorators.js +11 -0
- package/src/lib/bpm-auth/bpm-auth.decorators.js.map +1 -0
- package/src/lib/bpm-auth/bpm-auth.guard.d.ts +7 -0
- package/src/lib/bpm-auth/bpm-auth.guard.js +24 -0
- package/src/lib/bpm-auth/bpm-auth.guard.js.map +1 -0
- package/src/lib/bpm-auth/bpm-auth.module.d.ts +6 -0
- package/src/lib/bpm-auth/bpm-auth.module.js +69 -0
- package/src/lib/bpm-auth/bpm-auth.module.js.map +1 -0
- package/src/lib/bpm-auth/bpm-auth.options.d.ts +11 -0
- package/src/lib/bpm-auth/bpm-auth.options.js +3 -0
- package/src/lib/bpm-auth/bpm-auth.options.js.map +1 -0
- package/src/lib/bpm-auth/configurable-bpm-auth-context.accessor.d.ts +10 -0
- package/src/lib/bpm-auth/configurable-bpm-auth-context.accessor.js +40 -0
- package/src/lib/bpm-auth/configurable-bpm-auth-context.accessor.js.map +1 -0
- package/src/lib/bpm-auth/index.js +10 -0
- package/src/lib/bpm-auth/index.js.map +1 -0
- package/src/lib/common/filters/all-exceptions.filter.d.ts +25 -0
- package/src/lib/common/filters/all-exceptions.filter.js +93 -0
- package/src/lib/common/filters/all-exceptions.filter.js.map +1 -0
- package/src/lib/common/index.js +5 -0
- package/src/lib/common/index.js.map +1 -0
- package/src/lib/common/iso-duration.d.ts +1 -0
- package/src/lib/common/iso-duration.js +27 -0
- package/src/lib/common/iso-duration.js.map +1 -0
- package/src/lib/condition/condition.module.d.ts +2 -0
- package/src/lib/condition/condition.module.js +16 -0
- package/src/lib/condition/condition.module.js.map +1 -0
- package/src/lib/condition/condition.service.d.ts +16 -0
- package/src/lib/condition/condition.service.js +82 -0
- package/src/lib/condition/condition.service.js.map +1 -0
- package/src/lib/condition/index.js +6 -0
- package/src/lib/condition/index.js.map +1 -0
- package/src/lib/database/data-source.d.ts +4 -0
- package/src/lib/database/data-source.js +7 -0
- package/src/lib/database/data-source.js.map +1 -0
- package/src/lib/database/index.js +6 -0
- package/src/lib/database/index.js.map +1 -0
- package/src/lib/database/migration-runner.d.ts +1 -0
- package/src/lib/database/migration-runner.js +25 -0
- package/src/lib/database/migration-runner.js.map +1 -0
- package/src/lib/database/reconcile-legacy-migrations.d.ts +13 -0
- package/src/lib/database/reconcile-legacy-migrations.js +70 -0
- package/src/lib/database/reconcile-legacy-migrations.js.map +1 -0
- package/src/lib/database/typeorm.config.d.ts +14 -0
- package/src/lib/database/typeorm.config.js +135 -0
- package/src/lib/database/typeorm.config.js.map +1 -0
- package/src/lib/delegation/delegation-rule.entity.d.ts +19 -0
- package/src/lib/delegation/delegation-rule.entity.js +99 -0
- package/src/lib/delegation/delegation-rule.entity.js.map +1 -0
- package/src/lib/delegation/delegation.enums.d.ts +10 -0
- package/src/lib/delegation/delegation.enums.js +23 -0
- package/src/lib/delegation/delegation.enums.js.map +1 -0
- package/src/lib/delegation/delegation.module.d.ts +2 -0
- package/src/lib/delegation/delegation.module.js +22 -0
- package/src/lib/delegation/delegation.module.js.map +1 -0
- package/src/lib/delegation/delegation.mutations.d.ts +11 -0
- package/src/lib/delegation/delegation.mutations.js +87 -0
- package/src/lib/delegation/delegation.mutations.js.map +1 -0
- package/src/lib/delegation/delegation.queries.d.ts +10 -0
- package/src/lib/delegation/delegation.queries.js +91 -0
- package/src/lib/delegation/delegation.queries.js.map +1 -0
- package/src/lib/delegation/delegation.service.d.ts +56 -0
- package/src/lib/delegation/delegation.service.js +268 -0
- package/src/lib/delegation/delegation.service.js.map +1 -0
- package/src/lib/delegation/dto/delegation-rule.input.d.ts +24 -0
- package/src/lib/delegation/dto/delegation-rule.input.js +143 -0
- package/src/lib/delegation/dto/delegation-rule.input.js.map +1 -0
- package/src/lib/delegation/index.js +9 -0
- package/src/lib/delegation/index.js.map +1 -0
- package/src/lib/form/dto/form-definition.input.d.ts +26 -0
- package/src/lib/form/dto/form-definition.input.js +126 -0
- package/src/lib/form/dto/form-definition.input.js.map +1 -0
- package/src/lib/form/form-definition-version.entity.d.ts +17 -0
- package/src/lib/form/form-definition-version.entity.js +84 -0
- package/src/lib/form/form-definition-version.entity.js.map +1 -0
- package/src/lib/form/form-definition.entity.d.ts +13 -0
- package/src/lib/form/form-definition.entity.js +66 -0
- package/src/lib/form/form-definition.entity.js.map +1 -0
- package/src/lib/form/form-schema-lint.object.d.ts +4 -0
- package/src/lib/form/form-schema-lint.object.js +20 -0
- package/src/lib/form/form-schema-lint.object.js.map +1 -0
- package/src/lib/form/form-schema.validator.d.ts +13 -0
- package/src/lib/form/form-schema.validator.js +354 -0
- package/src/lib/form/form-schema.validator.js.map +1 -0
- package/src/lib/form/form.enums.d.ts +9 -0
- package/src/lib/form/form.enums.js +22 -0
- package/src/lib/form/form.enums.js.map +1 -0
- package/src/lib/form/form.module.d.ts +2 -0
- package/src/lib/form/form.module.js +27 -0
- package/src/lib/form/form.module.js.map +1 -0
- package/src/lib/form/form.mutations.d.ts +14 -0
- package/src/lib/form/form.mutations.js +89 -0
- package/src/lib/form/form.mutations.js.map +1 -0
- package/src/lib/form/form.queries.d.ts +16 -0
- package/src/lib/form/form.queries.js +96 -0
- package/src/lib/form/form.queries.js.map +1 -0
- package/src/lib/form/form.service.d.ts +49 -0
- package/src/lib/form/form.service.js +346 -0
- package/src/lib/form/form.service.js.map +1 -0
- package/src/lib/form/index.js +12 -0
- package/src/lib/form/index.js.map +1 -0
- package/src/lib/identity/identity-options.d.ts +13 -0
- package/src/lib/identity/identity-options.js +20 -0
- package/src/lib/identity/identity-options.js.map +1 -0
- package/src/lib/identity/identity.module.d.ts +17 -0
- package/src/lib/identity/identity.module.js +65 -0
- package/src/lib/identity/identity.module.js.map +1 -0
- package/src/lib/identity/identity.queries.d.ts +12 -0
- package/src/lib/identity/identity.queries.js +79 -0
- package/src/lib/identity/identity.queries.js.map +1 -0
- package/src/lib/identity/identity.service.d.ts +31 -0
- package/src/lib/identity/identity.service.js +196 -0
- package/src/lib/identity/identity.service.js.map +1 -0
- package/src/lib/identity/index.js +8 -0
- package/src/lib/identity/index.js.map +1 -0
- package/src/lib/identity/member-base.adapter.d.ts +123 -0
- package/src/lib/identity/member-base.adapter.js +147 -0
- package/src/lib/identity/member-base.adapter.js.map +1 -0
- package/src/lib/identity/member-metadata-cache.entity.d.ts +8 -0
- package/src/lib/identity/member-metadata-cache.entity.js +39 -0
- package/src/lib/identity/member-metadata-cache.entity.js.map +1 -0
- package/src/lib/identity/{member-not-found.exception.ts → member-not-found.exception.d.ts} +3 -8
- package/src/lib/identity/member-not-found.exception.js +26 -0
- package/src/lib/identity/member-not-found.exception.js.map +1 -0
- package/src/lib/identity/member-profile.object.d.ts +8 -0
- package/src/lib/identity/member-profile.object.js +37 -0
- package/src/lib/identity/member-profile.object.js.map +1 -0
- package/src/lib/identity/{member-resolver.interface.ts → member-resolver.interface.d.ts} +41 -55
- package/src/lib/identity/member-resolver.interface.js +19 -0
- package/src/lib/identity/member-resolver.interface.js.map +1 -0
- package/src/lib/migrations/0000000000001-enable-postgres-extensions.d.ts +6 -0
- package/src/lib/migrations/0000000000001-enable-postgres-extensions.js +17 -0
- package/src/lib/migrations/0000000000001-enable-postgres-extensions.js.map +1 -0
- package/src/lib/migrations/0000000001000-identity-organization-foundation.d.ts +6 -0
- package/src/lib/migrations/{0000000001000-identity-organization-foundation.ts → 0000000001000-identity-organization-foundation.js} +29 -38
- package/src/lib/migrations/0000000001000-identity-organization-foundation.js.map +1 -0
- package/src/lib/migrations/0000000002000-form-builder-foundation.d.ts +6 -0
- package/src/lib/migrations/{0000000002000-form-builder-foundation.ts → 0000000002000-form-builder-foundation.js} +21 -22
- package/src/lib/migrations/0000000002000-form-builder-foundation.js.map +1 -0
- package/src/lib/migrations/0000000003000-approval-template-foundation.d.ts +6 -0
- package/src/lib/migrations/{0000000003000-approval-template-foundation.ts → 0000000003000-approval-template-foundation.js} +21 -24
- package/src/lib/migrations/0000000003000-approval-template-foundation.js.map +1 -0
- package/src/lib/migrations/0000000004000-workflow-engine-foundation.d.ts +6 -0
- package/src/lib/migrations/{0000000004000-workflow-engine-foundation.ts → 0000000004000-workflow-engine-foundation.js} +34 -46
- package/src/lib/migrations/0000000004000-workflow-engine-foundation.js.map +1 -0
- package/src/lib/migrations/0000000005000-delegation-rules.d.ts +6 -0
- package/src/lib/migrations/{0000000005000-delegation-rules.ts → 0000000005000-delegation-rules.js} +19 -20
- package/src/lib/migrations/0000000005000-delegation-rules.js.map +1 -0
- package/src/lib/migrations/0000000006000-notifications-sla.d.ts +6 -0
- package/src/lib/migrations/{0000000006000-notifications-sla.ts → 0000000006000-notifications-sla.js} +23 -26
- package/src/lib/migrations/0000000006000-notifications-sla.js.map +1 -0
- package/src/lib/migrations/0000000007000-signatures-attachments.d.ts +6 -0
- package/src/lib/migrations/{0000000007000-signatures-attachments.ts → 0000000007000-signatures-attachments.js} +27 -28
- package/src/lib/migrations/0000000007000-signatures-attachments.js.map +1 -0
- package/src/lib/migrations/0000000008000-approval-template-categories.d.ts +6 -0
- package/src/lib/migrations/{0000000008000-approval-template-categories.ts → 0000000008000-approval-template-categories.js} +25 -28
- package/src/lib/migrations/0000000008000-approval-template-categories.js.map +1 -0
- package/src/lib/migrations/0000000009000-task-candidates.d.ts +6 -0
- package/src/lib/migrations/{0000000009000-task-candidates.ts → 0000000009000-task-candidates.js} +23 -26
- package/src/lib/migrations/0000000009000-task-candidates.js.map +1 -0
- package/src/lib/migrations/0000000010000-notification-delivery-state.d.ts +6 -0
- package/src/lib/migrations/{0000000010000-notification-delivery-state.ts → 0000000010000-notification-delivery-state.js} +17 -16
- package/src/lib/migrations/0000000010000-notification-delivery-state.js.map +1 -0
- package/src/lib/migrations/0000000011000-remove-attachment-encryption-key.d.ts +6 -0
- package/src/lib/migrations/0000000011000-remove-attachment-encryption-key.js +22 -0
- package/src/lib/migrations/0000000011000-remove-attachment-encryption-key.js.map +1 -0
- package/src/lib/migrations/0000000012000-notification-sla-idempotency.d.ts +6 -0
- package/src/lib/migrations/0000000012000-notification-sla-idempotency.js +21 -0
- package/src/lib/migrations/0000000012000-notification-sla-idempotency.js.map +1 -0
- package/src/lib/migrations/0000000013000-workflow-query-indexes.d.ts +6 -0
- package/src/lib/migrations/0000000013000-workflow-query-indexes.js +80 -0
- package/src/lib/migrations/0000000013000-workflow-query-indexes.js.map +1 -0
- package/src/lib/migrations/0000000014000-notification-resolution.d.ts +6 -0
- package/src/lib/migrations/0000000014000-notification-resolution.js +30 -0
- package/src/lib/migrations/0000000014000-notification-resolution.js.map +1 -0
- package/src/lib/migrations/0000000015000-backfill-stale-notification-resolution.d.ts +15 -0
- package/src/lib/migrations/{0000000015000-backfill-stale-notification-resolution.ts → 0000000015000-backfill-stale-notification-resolution.js} +16 -15
- package/src/lib/migrations/0000000015000-backfill-stale-notification-resolution.js.map +1 -0
- package/src/lib/migrations/0000000016000-archive-parallel-form-drafts.d.ts +15 -0
- package/src/lib/migrations/{0000000016000-archive-parallel-form-drafts.ts → 0000000016000-archive-parallel-form-drafts.js} +16 -15
- package/src/lib/migrations/0000000016000-archive-parallel-form-drafts.js.map +1 -0
- package/src/lib/migrations/0000000017000-adhoc-directives.d.ts +6 -0
- package/src/lib/migrations/{0000000017000-adhoc-directives.ts → 0000000017000-adhoc-directives.js} +19 -18
- package/src/lib/migrations/0000000017000-adhoc-directives.js.map +1 -0
- package/src/lib/migrations/index.d.ts +20 -0
- package/src/lib/migrations/index.js +61 -0
- package/src/lib/migrations/index.js.map +1 -0
- package/src/lib/notification/dto/notification-preference.input.d.ts +9 -0
- package/src/lib/notification/dto/notification-preference.input.js +53 -0
- package/src/lib/notification/dto/notification-preference.input.js.map +1 -0
- package/src/lib/notification/index.js +11 -0
- package/src/lib/notification/index.js.map +1 -0
- package/src/lib/notification/notification-delivery-scheduler.service.d.ts +13 -0
- package/src/lib/notification/notification-delivery-scheduler.service.js +53 -0
- package/src/lib/notification/notification-delivery-scheduler.service.js.map +1 -0
- package/src/lib/notification/notification-delivery.service.d.ts +23 -0
- package/src/lib/notification/notification-delivery.service.js +245 -0
- package/src/lib/notification/notification-delivery.service.js.map +1 -0
- package/src/lib/notification/notification-dispatcher.token.d.ts +7 -0
- package/src/lib/notification/notification-dispatcher.token.js +5 -0
- package/src/lib/notification/notification-dispatcher.token.js.map +1 -0
- package/src/lib/notification/notification-options.d.ts +215 -0
- package/src/lib/notification/notification-options.js +128 -0
- package/src/lib/notification/notification-options.js.map +1 -0
- package/src/lib/notification/notification-options.module.d.ts +23 -0
- package/src/lib/notification/notification-options.module.js +41 -0
- package/src/lib/notification/notification-options.module.js.map +1 -0
- package/src/lib/notification/notification-preference.entity.d.ts +10 -0
- package/src/lib/notification/notification-preference.entity.js +50 -0
- package/src/lib/notification/notification-preference.entity.js.map +1 -0
- package/src/lib/notification/notification-sla-scheduler.service.d.ts +13 -0
- package/src/lib/notification/notification-sla-scheduler.service.js +51 -0
- package/src/lib/notification/notification-sla-scheduler.service.js.map +1 -0
- package/src/lib/notification/notification-template.d.ts +13 -0
- package/src/lib/notification/notification-template.js +61 -0
- package/src/lib/notification/notification-template.js.map +1 -0
- package/src/lib/notification/notification.entity.d.ts +31 -0
- package/src/lib/notification/notification.entity.js +149 -0
- package/src/lib/notification/notification.entity.js.map +1 -0
- package/src/lib/notification/notification.enums.d.ts +38 -0
- package/src/lib/notification/notification.enums.js +63 -0
- package/src/lib/notification/notification.enums.js.map +1 -0
- package/src/lib/notification/notification.module.d.ts +2 -0
- package/src/lib/notification/notification.module.js +45 -0
- package/src/lib/notification/notification.module.js.map +1 -0
- package/src/lib/notification/notification.mutations.d.ts +11 -0
- package/src/lib/notification/notification.mutations.js +64 -0
- package/src/lib/notification/notification.mutations.js.map +1 -0
- package/src/lib/notification/notification.queries.d.ts +11 -0
- package/src/lib/notification/notification.queries.js +77 -0
- package/src/lib/notification/notification.queries.js.map +1 -0
- package/src/lib/notification/notification.service.d.ts +121 -0
- package/src/lib/notification/notification.service.js +713 -0
- package/src/lib/notification/notification.service.js.map +1 -0
- package/src/lib/organization/dto/manager-resolution.input.d.ts +18 -0
- package/src/lib/organization/dto/manager-resolution.input.js +107 -0
- package/src/lib/organization/dto/manager-resolution.input.js.map +1 -0
- package/src/lib/organization/dto/membership.input.d.ts +16 -0
- package/src/lib/organization/dto/membership.input.js +99 -0
- package/src/lib/organization/dto/membership.input.js.map +1 -0
- package/src/lib/organization/dto/org-unit.input.d.ts +24 -0
- package/src/lib/organization/dto/org-unit.input.js +119 -0
- package/src/lib/organization/dto/org-unit.input.js.map +1 -0
- package/src/lib/organization/dto/position.input.d.ts +13 -0
- package/src/lib/organization/dto/position.input.js +70 -0
- package/src/lib/organization/dto/position.input.js.map +1 -0
- package/src/lib/organization/index.js +16 -0
- package/src/lib/organization/index.js.map +1 -0
- package/src/lib/organization/json-metadata.d.ts +1 -0
- package/src/lib/organization/json-metadata.js +14 -0
- package/src/lib/organization/json-metadata.js.map +1 -0
- package/src/lib/organization/manager-resolution.entity.d.ts +11 -0
- package/src/lib/organization/manager-resolution.entity.js +55 -0
- package/src/lib/organization/manager-resolution.entity.js.map +1 -0
- package/src/lib/organization/membership.entity.d.ts +11 -0
- package/src/lib/organization/membership.entity.js +60 -0
- package/src/lib/organization/membership.entity.js.map +1 -0
- package/src/lib/organization/org-unit-tree-commit-result.object.d.ts +4 -0
- package/src/lib/organization/org-unit-tree-commit-result.object.js +17 -0
- package/src/lib/organization/org-unit-tree-commit-result.object.js.map +1 -0
- package/src/lib/organization/org-unit.entity.d.ts +13 -0
- package/src/lib/organization/org-unit.entity.js +64 -0
- package/src/lib/organization/org-unit.entity.js.map +1 -0
- package/src/lib/organization/organization-summary.object.d.ts +6 -0
- package/src/lib/organization/organization-summary.object.js +28 -0
- package/src/lib/organization/organization-summary.object.js.map +1 -0
- package/src/lib/organization/organization.enums.d.ts +11 -0
- package/src/lib/organization/organization.enums.js +22 -0
- package/src/lib/organization/organization.enums.js.map +1 -0
- package/src/lib/organization/organization.module.d.ts +2 -0
- package/src/lib/organization/organization.module.js +35 -0
- package/src/lib/organization/organization.module.js.map +1 -0
- package/src/lib/organization/organization.mutations.d.ts +26 -0
- package/src/lib/organization/organization.mutations.js +148 -0
- package/src/lib/organization/organization.mutations.js.map +1 -0
- package/src/lib/organization/organization.queries.d.ts +23 -0
- package/src/lib/organization/organization.queries.js +204 -0
- package/src/lib/organization/organization.queries.js.map +1 -0
- package/src/lib/organization/organization.service.d.ts +103 -0
- package/src/lib/organization/organization.service.js +768 -0
- package/src/lib/organization/organization.service.js.map +1 -0
- package/src/lib/organization/position.entity.d.ts +9 -0
- package/src/lib/organization/position.entity.js +48 -0
- package/src/lib/organization/position.entity.js.map +1 -0
- package/src/lib/organization/resolved-manager.object.d.ts +4 -0
- package/src/lib/organization/resolved-manager.object.js +20 -0
- package/src/lib/organization/resolved-manager.object.js.map +1 -0
- package/src/lib/signature/index.js +8 -0
- package/src/lib/signature/index.js.map +1 -0
- package/src/lib/signature/signature-options.d.ts +33 -0
- package/src/lib/signature/signature-options.js +51 -0
- package/src/lib/signature/signature-options.js.map +1 -0
- package/src/lib/signature/signature-verification.object.d.ts +6 -0
- package/src/lib/signature/signature-verification.object.js +28 -0
- package/src/lib/signature/signature-verification.object.js.map +1 -0
- package/src/lib/signature/signature.entity.d.ts +16 -0
- package/src/lib/signature/signature.entity.js +88 -0
- package/src/lib/signature/signature.entity.js.map +1 -0
- package/src/lib/signature/signature.module.d.ts +11 -0
- package/src/lib/signature/signature.module.js +60 -0
- package/src/lib/signature/signature.module.js.map +1 -0
- package/src/lib/signature/signature.queries.d.ts +11 -0
- package/src/lib/signature/signature.queries.js +46 -0
- package/src/lib/signature/signature.queries.js.map +1 -0
- package/src/lib/signature/signature.service.d.ts +25 -0
- package/src/lib/signature/signature.service.js +124 -0
- package/src/lib/signature/signature.service.js.map +1 -0
- package/src/lib/template/approval-template-category.entity.d.ts +9 -0
- package/src/lib/template/approval-template-category.entity.js +49 -0
- package/src/lib/template/approval-template-category.entity.js.map +1 -0
- package/src/lib/template/approval-template-version.entity.d.ts +21 -0
- package/src/lib/template/approval-template-version.entity.js +106 -0
- package/src/lib/template/approval-template-version.entity.js.map +1 -0
- package/src/lib/template/approval-template.entity.d.ts +14 -0
- package/src/lib/template/approval-template.entity.js +74 -0
- package/src/lib/template/approval-template.entity.js.map +1 -0
- package/src/lib/template/compose-approval-template.object.d.ts +11 -0
- package/src/lib/template/compose-approval-template.object.js +36 -0
- package/src/lib/template/compose-approval-template.object.js.map +1 -0
- package/src/lib/template/compose-template.mutations.d.ts +8 -0
- package/src/lib/template/compose-template.mutations.js +32 -0
- package/src/lib/template/compose-template.mutations.js.map +1 -0
- package/src/lib/template/dto/approval-template.input.d.ts +36 -0
- package/src/lib/template/dto/approval-template.input.js +187 -0
- package/src/lib/template/dto/approval-template.input.js.map +1 -0
- package/src/lib/template/dto/compose-approval-template.input.d.ts +17 -0
- package/src/lib/template/dto/compose-approval-template.input.js +99 -0
- package/src/lib/template/dto/compose-approval-template.input.js.map +1 -0
- package/src/lib/template/index.js +14 -0
- package/src/lib/template/index.js.map +1 -0
- package/src/lib/template/template.enums.d.ts +14 -0
- package/src/lib/template/template.enums.js +31 -0
- package/src/lib/template/template.enums.js.map +1 -0
- package/src/lib/template/template.module.d.ts +2 -0
- package/src/lib/template/template.module.js +41 -0
- package/src/lib/template/template.module.js.map +1 -0
- package/src/lib/template/template.mutations.d.ts +20 -0
- package/src/lib/template/template.mutations.js +139 -0
- package/src/lib/template/template.mutations.js.map +1 -0
- package/src/lib/template/template.queries.d.ts +16 -0
- package/src/lib/template/template.queries.js +133 -0
- package/src/lib/template/template.queries.js.map +1 -0
- package/src/lib/template/template.service.d.ts +83 -0
- package/src/lib/template/template.service.js +654 -0
- package/src/lib/template/template.service.js.map +1 -0
- package/src/lib/template/workflow-definition.validator.d.ts +9 -0
- package/src/lib/template/workflow-definition.validator.js +362 -0
- package/src/lib/template/workflow-definition.validator.js.map +1 -0
- package/src/lib/testing/cel-js.jest.d.ts +15 -0
- package/src/lib/testing/cel-js.jest.js +27 -0
- package/src/lib/testing/cel-js.jest.js.map +1 -0
- package/src/lib/workflow-engine/activity-log.entity.d.ts +12 -0
- package/src/lib/workflow-engine/activity-log.entity.js +62 -0
- package/src/lib/workflow-engine/activity-log.entity.js.map +1 -0
- package/src/lib/workflow-engine/adhoc-directive.entity.d.ts +31 -0
- package/src/lib/workflow-engine/adhoc-directive.entity.js +105 -0
- package/src/lib/workflow-engine/adhoc-directive.entity.js.map +1 -0
- package/src/lib/workflow-engine/adhoc.enums.d.ts +21 -0
- package/src/lib/workflow-engine/adhoc.enums.js +42 -0
- package/src/lib/workflow-engine/adhoc.enums.js.map +1 -0
- package/src/lib/workflow-engine/approval-instance-page-info.object.d.ts +8 -0
- package/src/lib/workflow-engine/approval-instance-page-info.object.js +36 -0
- package/src/lib/workflow-engine/approval-instance-page-info.object.js.map +1 -0
- package/src/lib/workflow-engine/approval-instance.entity.d.ts +22 -0
- package/src/lib/workflow-engine/approval-instance.entity.js +113 -0
- package/src/lib/workflow-engine/approval-instance.entity.js.map +1 -0
- package/src/lib/workflow-engine/dto/adhoc-notification.input.d.ts +6 -0
- package/src/lib/workflow-engine/dto/adhoc-notification.input.js +28 -0
- package/src/lib/workflow-engine/dto/adhoc-notification.input.js.map +1 -0
- package/src/lib/workflow-engine/dto/adhoc-target.input.d.ts +16 -0
- package/src/lib/workflow-engine/dto/adhoc-target.input.js +61 -0
- package/src/lib/workflow-engine/dto/adhoc-target.input.js.map +1 -0
- package/src/lib/workflow-engine/dto/cancel-approval-instance.input.d.ts +5 -0
- package/src/lib/workflow-engine/dto/cancel-approval-instance.input.js +29 -0
- package/src/lib/workflow-engine/dto/cancel-approval-instance.input.js.map +1 -0
- package/src/lib/workflow-engine/dto/decide-task.input.d.ts +9 -0
- package/src/lib/workflow-engine/dto/decide-task.input.js +47 -0
- package/src/lib/workflow-engine/dto/decide-task.input.js.map +1 -0
- package/src/lib/workflow-engine/dto/dry-run-approval-workflow.input.d.ts +6 -0
- package/src/lib/workflow-engine/dto/dry-run-approval-workflow.input.js +34 -0
- package/src/lib/workflow-engine/dto/dry-run-approval-workflow.input.js.map +1 -0
- package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.d.ts +6 -0
- package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.js +34 -0
- package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.js.map +1 -0
- package/src/lib/workflow-engine/dto/submit-approval-instance.input.d.ts +7 -0
- package/src/lib/workflow-engine/dto/submit-approval-instance.input.js +40 -0
- package/src/lib/workflow-engine/dto/submit-approval-instance.input.js.map +1 -0
- package/src/lib/workflow-engine/index.js +28 -0
- package/src/lib/workflow-engine/index.js.map +1 -0
- package/src/lib/workflow-engine/task-candidate.entity.d.ts +14 -0
- package/src/lib/workflow-engine/task-candidate.entity.js +72 -0
- package/src/lib/workflow-engine/task-candidate.entity.js.map +1 -0
- package/src/lib/workflow-engine/task-decision.entity.d.ts +12 -0
- package/src/lib/workflow-engine/task-decision.entity.js +60 -0
- package/src/lib/workflow-engine/task-decision.entity.js.map +1 -0
- package/src/lib/workflow-engine/task.entity.d.ts +25 -0
- package/src/lib/workflow-engine/task.entity.js +130 -0
- package/src/lib/workflow-engine/task.entity.js.map +1 -0
- package/src/lib/workflow-engine/workflow-condition-evaluator.d.ts +5 -0
- package/src/lib/workflow-engine/workflow-condition-evaluator.js +107 -0
- package/src/lib/workflow-engine/workflow-condition-evaluator.js.map +1 -0
- package/src/lib/workflow-engine/workflow-dashboard-summary.object.d.ts +8 -0
- package/src/lib/workflow-engine/workflow-dashboard-summary.object.js +36 -0
- package/src/lib/workflow-engine/workflow-dashboard-summary.object.js.map +1 -0
- package/src/lib/workflow-engine/workflow-dry-run.object.d.ts +21 -0
- package/src/lib/workflow-engine/workflow-dry-run.object.js +86 -0
- package/src/lib/workflow-engine/workflow-dry-run.object.js.map +1 -0
- package/src/lib/workflow-engine/workflow-engine.enums.d.ts +61 -0
- package/src/lib/workflow-engine/workflow-engine.enums.js +98 -0
- package/src/lib/workflow-engine/workflow-engine.enums.js.map +1 -0
- package/src/lib/workflow-engine/workflow-engine.module.d.ts +9 -0
- package/src/lib/workflow-engine/workflow-engine.module.js +78 -0
- package/src/lib/workflow-engine/workflow-engine.module.js.map +1 -0
- package/src/lib/workflow-engine/workflow-engine.mutations.d.ts +29 -0
- package/src/lib/workflow-engine/workflow-engine.mutations.js +218 -0
- package/src/lib/workflow-engine/workflow-engine.mutations.js.map +1 -0
- package/src/lib/workflow-engine/workflow-engine.queries.d.ts +31 -0
- package/src/lib/workflow-engine/workflow-engine.queries.js +237 -0
- package/src/lib/workflow-engine/workflow-engine.queries.js.map +1 -0
- package/src/lib/workflow-engine/workflow-engine.service.d.ts +188 -0
- package/src/lib/workflow-engine/workflow-engine.service.js +3561 -0
- package/src/lib/workflow-engine/workflow-engine.service.js.map +1 -0
- package/src/lib/workflow-engine/{workflow-engine.tokens.ts → workflow-engine.tokens.d.ts} +3 -9
- package/src/lib/workflow-engine/workflow-engine.tokens.js +5 -0
- package/src/lib/workflow-engine/workflow-engine.tokens.js.map +1 -0
- package/src/lib/workflow-engine/workflow-service-task-dispatcher.token.d.ts +18 -0
- package/src/lib/workflow-engine/workflow-service-task-dispatcher.token.js +39 -0
- package/src/lib/workflow-engine/workflow-service-task-dispatcher.token.js.map +1 -0
- package/src/lib/workflow-engine/workflow-token.entity.d.ts +10 -0
- package/src/lib/workflow-engine/workflow-token.entity.js +50 -0
- package/src/lib/workflow-engine/workflow-token.entity.js.map +1 -0
- package/eslint.config.mjs +0 -20
- package/jest.config.cts +0 -15
- package/project.json +0 -58
- package/src/lib/attachment/attachment-options.spec.ts +0 -45
- package/src/lib/attachment/attachment-options.ts +0 -130
- package/src/lib/attachment/attachment-storage.provider.spec.ts +0 -79
- package/src/lib/attachment/attachment-storage.provider.ts +0 -60
- package/src/lib/attachment/attachment.controller.ts +0 -39
- package/src/lib/attachment/attachment.entity.ts +0 -59
- package/src/lib/attachment/attachment.module.ts +0 -149
- package/src/lib/attachment/attachment.mutations.ts +0 -22
- package/src/lib/attachment/attachment.queries.ts +0 -65
- package/src/lib/attachment/attachment.service.spec.ts +0 -319
- package/src/lib/attachment/attachment.service.ts +0 -477
- package/src/lib/attachment/dto/upload-attachment.input.ts +0 -60
- package/src/lib/bpm/bpm-root.module.spec.ts +0 -138
- package/src/lib/bpm/bpm-root.module.ts +0 -320
- package/src/lib/bpm-auth/bpm-auth-context.extractor.ts +0 -106
- package/src/lib/bpm-auth/bpm-auth-context.ts +0 -40
- package/src/lib/bpm-auth/bpm-auth.authorization.ts +0 -80
- package/src/lib/bpm-auth/bpm-auth.decorators.ts +0 -19
- package/src/lib/bpm-auth/bpm-auth.guard.ts +0 -31
- package/src/lib/bpm-auth/bpm-auth.module.spec.ts +0 -106
- package/src/lib/bpm-auth/bpm-auth.module.ts +0 -69
- package/src/lib/bpm-auth/bpm-auth.options.ts +0 -21
- package/src/lib/bpm-auth/configurable-bpm-auth-context.accessor.ts +0 -55
- package/src/lib/common/filters/all-exceptions.filter.ts +0 -138
- package/src/lib/common/iso-duration.ts +0 -31
- package/src/lib/condition/condition.module.ts +0 -8
- package/src/lib/condition/condition.service.spec.ts +0 -50
- package/src/lib/condition/condition.service.ts +0 -141
- package/src/lib/database/data-source.ts +0 -7
- package/src/lib/database/migration-runner.ts +0 -26
- package/src/lib/database/reconcile-legacy-migrations.ts +0 -87
- package/src/lib/database/typeorm.config.ts +0 -217
- package/src/lib/delegation/delegation-rule.entity.ts +0 -84
- package/src/lib/delegation/delegation.enums.ts +0 -21
- package/src/lib/delegation/delegation.module.ts +0 -14
- package/src/lib/delegation/delegation.mutations.ts +0 -92
- package/src/lib/delegation/delegation.queries.ts +0 -111
- package/src/lib/delegation/delegation.service.spec.ts +0 -295
- package/src/lib/delegation/delegation.service.ts +0 -482
- package/src/lib/delegation/dto/delegation-rule.input.ts +0 -125
- package/src/lib/form/dto/form-definition.input.ts +0 -90
- package/src/lib/form/form-definition-version.entity.ts +0 -66
- package/src/lib/form/form-definition.entity.ts +0 -54
- package/src/lib/form/form-schema-lint.object.ts +0 -10
- package/src/lib/form/form-schema.validator.spec.ts +0 -180
- package/src/lib/form/form-schema.validator.ts +0 -538
- package/src/lib/form/form.enums.ts +0 -20
- package/src/lib/form/form.module.ts +0 -19
- package/src/lib/form/form.mutations.ts +0 -73
- package/src/lib/form/form.queries.ts +0 -72
- package/src/lib/form/form.service.spec.ts +0 -371
- package/src/lib/form/form.service.ts +0 -607
- package/src/lib/identity/identity-options.ts +0 -41
- package/src/lib/identity/identity.module.ts +0 -96
- package/src/lib/identity/identity.queries.ts +0 -61
- package/src/lib/identity/identity.service.spec.ts +0 -328
- package/src/lib/identity/identity.service.ts +0 -277
- package/src/lib/identity/member-base.adapter.spec.ts +0 -122
- package/src/lib/identity/member-base.adapter.ts +0 -302
- package/src/lib/identity/member-metadata-cache.entity.ts +0 -27
- package/src/lib/identity/member-profile.object.ts +0 -28
- package/src/lib/migrations/0000000000001-enable-postgres-extensions.ts +0 -14
- package/src/lib/migrations/0000000011000-remove-attachment-encryption-key.ts +0 -19
- package/src/lib/migrations/0000000012000-notification-sla-idempotency.ts +0 -22
- package/src/lib/migrations/0000000013000-workflow-query-indexes.ts +0 -113
- package/src/lib/migrations/0000000014000-notification-resolution.ts +0 -29
- package/src/lib/migrations/index.ts +0 -59
- package/src/lib/notification/dto/notification-preference.input.ts +0 -46
- package/src/lib/notification/notification-delivery-scheduler.service.ts +0 -69
- package/src/lib/notification/notification-delivery.service.spec.ts +0 -315
- package/src/lib/notification/notification-delivery.service.ts +0 -339
- package/src/lib/notification/notification-dispatcher.token.ts +0 -13
- package/src/lib/notification/notification-options.module.ts +0 -67
- package/src/lib/notification/notification-options.spec.ts +0 -93
- package/src/lib/notification/notification-options.ts +0 -452
- package/src/lib/notification/notification-preference.entity.ts +0 -35
- package/src/lib/notification/notification-sla-scheduler.service.ts +0 -66
- package/src/lib/notification/notification-template.spec.ts +0 -33
- package/src/lib/notification/notification-template.ts +0 -97
- package/src/lib/notification/notification.entity.ts +0 -129
- package/src/lib/notification/notification.enums.ts +0 -64
- package/src/lib/notification/notification.module.ts +0 -37
- package/src/lib/notification/notification.mutations.ts +0 -47
- package/src/lib/notification/notification.queries.ts +0 -64
- package/src/lib/notification/notification.service.spec.ts +0 -412
- package/src/lib/notification/notification.service.ts +0 -1129
- package/src/lib/organization/dto/manager-resolution.input.ts +0 -91
- package/src/lib/organization/dto/membership.input.ts +0 -77
- package/src/lib/organization/dto/org-unit.input.ts +0 -96
- package/src/lib/organization/dto/position.input.ts +0 -50
- package/src/lib/organization/json-metadata.ts +0 -15
- package/src/lib/organization/manager-resolution.entity.ts +0 -44
- package/src/lib/organization/membership.entity.ts +0 -50
- package/src/lib/organization/org-unit-tree-commit-result.object.ts +0 -8
- package/src/lib/organization/org-unit.entity.ts +0 -53
- package/src/lib/organization/organization-summary.object.ts +0 -16
- package/src/lib/organization/organization.enums.ts +0 -19
- package/src/lib/organization/organization.module.ts +0 -27
- package/src/lib/organization/organization.mutations.ts +0 -115
- package/src/lib/organization/organization.queries.ts +0 -191
- package/src/lib/organization/organization.service.spec.ts +0 -835
- package/src/lib/organization/organization.service.ts +0 -1245
- package/src/lib/organization/position.entity.ts +0 -39
- package/src/lib/organization/resolved-manager.object.ts +0 -10
- package/src/lib/signature/signature-options.spec.ts +0 -60
- package/src/lib/signature/signature-options.ts +0 -120
- package/src/lib/signature/signature-verification.object.ts +0 -16
- package/src/lib/signature/signature.entity.ts +0 -62
- package/src/lib/signature/signature.module.ts +0 -80
- package/src/lib/signature/signature.queries.ts +0 -37
- package/src/lib/signature/signature.service.spec.ts +0 -134
- package/src/lib/signature/signature.service.ts +0 -205
- package/src/lib/template/approval-template-category.entity.ts +0 -40
- package/src/lib/template/approval-template-version.entity.ts +0 -82
- package/src/lib/template/approval-template.entity.ts +0 -64
- package/src/lib/template/compose-approval-template.object.ts +0 -23
- package/src/lib/template/compose-template.mutations.ts +0 -22
- package/src/lib/template/dto/approval-template.input.ts +0 -147
- package/src/lib/template/dto/compose-approval-template.input.ts +0 -80
- package/src/lib/template/template.enums.ts +0 -30
- package/src/lib/template/template.module.ts +0 -33
- package/src/lib/template/template.mutations.ts +0 -106
- package/src/lib/template/template.queries.ts +0 -119
- package/src/lib/template/template.service.spec.ts +0 -822
- package/src/lib/template/template.service.ts +0 -1082
- package/src/lib/template/workflow-definition.validator.spec.ts +0 -595
- package/src/lib/template/workflow-definition.validator.ts +0 -539
- package/src/lib/testing/cel-js.jest.ts +0 -46
- package/src/lib/workflow-engine/activity-log.entity.ts +0 -48
- package/src/lib/workflow-engine/adhoc-directive.entity.ts +0 -90
- package/src/lib/workflow-engine/adhoc.enums.ts +0 -42
- package/src/lib/workflow-engine/approval-instance-page-info.object.ts +0 -22
- package/src/lib/workflow-engine/approval-instance.entity.ts +0 -86
- package/src/lib/workflow-engine/dto/adhoc-notification.input.ts +0 -18
- package/src/lib/workflow-engine/dto/adhoc-target.input.ts +0 -52
- package/src/lib/workflow-engine/dto/cancel-approval-instance.input.ts +0 -18
- package/src/lib/workflow-engine/dto/decide-task.input.ts +0 -33
- package/src/lib/workflow-engine/dto/dry-run-approval-workflow.input.ts +0 -22
- package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.ts +0 -22
- package/src/lib/workflow-engine/dto/submit-approval-instance.input.ts +0 -27
- package/src/lib/workflow-engine/task-candidate.entity.ts +0 -56
- package/src/lib/workflow-engine/task-decision.entity.ts +0 -43
- package/src/lib/workflow-engine/task.entity.ts +0 -102
- package/src/lib/workflow-engine/workflow-condition-evaluator.ts +0 -168
- package/src/lib/workflow-engine/workflow-dashboard-summary.object.ts +0 -22
- package/src/lib/workflow-engine/workflow-dry-run.object.ts +0 -58
- package/src/lib/workflow-engine/workflow-engine.enums.ts +0 -102
- package/src/lib/workflow-engine/workflow-engine.module.ts +0 -77
- package/src/lib/workflow-engine/workflow-engine.mutations.ts +0 -193
- package/src/lib/workflow-engine/workflow-engine.queries.ts +0 -241
- package/src/lib/workflow-engine/workflow-engine.service.spec.ts +0 -3345
- package/src/lib/workflow-engine/workflow-engine.service.ts +0 -6022
- package/src/lib/workflow-engine/workflow-service-task-dispatcher.token.ts +0 -69
- package/src/lib/workflow-engine/workflow-token.entity.ts +0 -40
- package/tsconfig.json +0 -24
- package/tsconfig.lib.json +0 -16
- package/tsconfig.spec.json +0 -16
- /package/src/{index.ts → index.d.ts} +0 -0
- /package/src/lib/attachment/{index.ts → index.d.ts} +0 -0
- /package/src/lib/bpm/{index.ts → index.d.ts} +0 -0
- /package/src/lib/bpm-auth/{index.ts → index.d.ts} +0 -0
- /package/src/lib/common/{index.ts → index.d.ts} +0 -0
- /package/src/lib/condition/{index.ts → index.d.ts} +0 -0
- /package/src/lib/database/{index.ts → index.d.ts} +0 -0
- /package/src/lib/delegation/{index.ts → index.d.ts} +0 -0
- /package/src/lib/form/{index.ts → index.d.ts} +0 -0
- /package/src/lib/identity/{index.ts → index.d.ts} +0 -0
- /package/src/lib/notification/{index.ts → index.d.ts} +0 -0
- /package/src/lib/organization/{index.ts → index.d.ts} +0 -0
- /package/src/lib/signature/{index.ts → index.d.ts} +0 -0
- /package/src/lib/template/{index.ts → index.d.ts} +0 -0
- /package/src/lib/workflow-engine/{index.ts → index.d.ts} +0 -0
|
@@ -0,0 +1,3561 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WorkflowEngineService = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const typeorm_1 = require("@nestjs/typeorm");
|
|
7
|
+
const typeorm_2 = require("typeorm");
|
|
8
|
+
const attachment_service_1 = require("../attachment/attachment.service");
|
|
9
|
+
const delegation_service_1 = require("../delegation/delegation.service");
|
|
10
|
+
const form_definition_version_entity_1 = require("../form/form-definition-version.entity");
|
|
11
|
+
const form_enums_1 = require("../form/form.enums");
|
|
12
|
+
const notification_service_1 = require("../notification/notification.service");
|
|
13
|
+
const notification_entity_1 = require("../notification/notification.entity");
|
|
14
|
+
const notification_enums_1 = require("../notification/notification.enums");
|
|
15
|
+
const signature_service_1 = require("../signature/signature.service");
|
|
16
|
+
const condition_service_1 = require("../condition/condition.service");
|
|
17
|
+
const manager_resolution_entity_1 = require("../organization/manager-resolution.entity");
|
|
18
|
+
const membership_entity_1 = require("../organization/membership.entity");
|
|
19
|
+
const org_unit_entity_1 = require("../organization/org-unit.entity");
|
|
20
|
+
const organization_enums_1 = require("../organization/organization.enums");
|
|
21
|
+
const approval_template_version_entity_1 = require("../template/approval-template-version.entity");
|
|
22
|
+
const approval_template_entity_1 = require("../template/approval-template.entity");
|
|
23
|
+
const template_enums_1 = require("../template/template.enums");
|
|
24
|
+
const activity_log_entity_1 = require("./activity-log.entity");
|
|
25
|
+
const adhoc_directive_entity_1 = require("./adhoc-directive.entity");
|
|
26
|
+
const adhoc_enums_1 = require("./adhoc.enums");
|
|
27
|
+
const approval_instance_entity_1 = require("./approval-instance.entity");
|
|
28
|
+
const task_decision_entity_1 = require("./task-decision.entity");
|
|
29
|
+
const task_candidate_entity_1 = require("./task-candidate.entity");
|
|
30
|
+
const task_entity_1 = require("./task.entity");
|
|
31
|
+
const workflow_engine_enums_1 = require("./workflow-engine.enums");
|
|
32
|
+
const workflow_condition_evaluator_1 = require("./workflow-condition-evaluator");
|
|
33
|
+
const workflow_dry_run_object_1 = require("./workflow-dry-run.object");
|
|
34
|
+
const workflow_service_task_dispatcher_token_1 = require("./workflow-service-task-dispatcher.token");
|
|
35
|
+
const workflow_token_entity_1 = require("./workflow-token.entity");
|
|
36
|
+
const MAX_PROCESSING_STEPS = 500;
|
|
37
|
+
const DEFAULT_APPROVAL_HISTORY_TASK_LIMIT = 50;
|
|
38
|
+
const SYSTEM_DRY_RUN_INSTANCE_ID = 'dry-run-instance';
|
|
39
|
+
const WORKFLOW_READ_ALL_PERMISSIONS = new Set([
|
|
40
|
+
'bpm:*',
|
|
41
|
+
'bpm.workflow.read_all',
|
|
42
|
+
'bpm:workflow:read_all',
|
|
43
|
+
'workflow.read_all',
|
|
44
|
+
]);
|
|
45
|
+
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()) {
|
|
47
|
+
this.approvalInstanceRepository = approvalInstanceRepository;
|
|
48
|
+
this.workflowTokenRepository = workflowTokenRepository;
|
|
49
|
+
this.taskRepository = taskRepository;
|
|
50
|
+
this.taskCandidateRepository = taskCandidateRepository;
|
|
51
|
+
this.taskDecisionRepository = taskDecisionRepository;
|
|
52
|
+
this.notificationRepository = notificationRepository;
|
|
53
|
+
this.activityLogRepository = activityLogRepository;
|
|
54
|
+
this.adhocDirectiveRepository = adhocDirectiveRepository;
|
|
55
|
+
this.approvalTemplateRepository = approvalTemplateRepository;
|
|
56
|
+
this.approvalTemplateVersionRepository = approvalTemplateVersionRepository;
|
|
57
|
+
this.formDefinitionVersionRepository = formDefinitionVersionRepository;
|
|
58
|
+
this.attachmentService = attachmentService;
|
|
59
|
+
this.conditionService = conditionService;
|
|
60
|
+
this.delegationService = delegationService;
|
|
61
|
+
this.notificationService = notificationService;
|
|
62
|
+
this.signatureService = signatureService;
|
|
63
|
+
this.serviceTaskDispatcher = serviceTaskDispatcher;
|
|
64
|
+
}
|
|
65
|
+
async submitApprovalInstance(input) {
|
|
66
|
+
const formData = parseJsonObject(input.formDataJson, 'formDataJson');
|
|
67
|
+
const template = await this.getTemplateOrThrow(input.templateId);
|
|
68
|
+
if (!template.currentVersionId) {
|
|
69
|
+
throw new common_1.ConflictException('Approval template does not have a published version');
|
|
70
|
+
}
|
|
71
|
+
const templateVersion = await this.getPublishedTemplateVersionOrThrow(template.currentVersionId);
|
|
72
|
+
const formDefinitionVersion = await this.getPublishedFormVersionOrThrow(templateVersion.formDefinitionVersionId);
|
|
73
|
+
validateSubmittedFormData(formDefinitionVersion.schema, formData);
|
|
74
|
+
const initiatorMetadataSnapshot = input.initiatorMetadataSnapshotJson
|
|
75
|
+
? parseJsonObject(input.initiatorMetadataSnapshotJson, 'initiatorMetadataSnapshotJson')
|
|
76
|
+
: await this.readDefaultInitiatorMetadataSnapshot(input.initiatorMemberId);
|
|
77
|
+
if (!this.conditionService.evaluateBoolean(templateVersion.initiatorPolicyCel, buildInitiatorPolicyContext(initiatorMetadataSnapshot), 'initiatorPolicyCel')) {
|
|
78
|
+
throw new common_1.ConflictException('Current member is not allowed to start this approval template');
|
|
79
|
+
}
|
|
80
|
+
const startedAt = new Date();
|
|
81
|
+
const startNode = templateVersion.workflowDefinition.nodes.find((node) => node.type === 'startEvent');
|
|
82
|
+
if (!startNode) {
|
|
83
|
+
throw new common_1.BadRequestException('Published workflow does not include a startEvent');
|
|
84
|
+
}
|
|
85
|
+
return this.approvalInstanceRepository.manager.transaction(async (manager) => {
|
|
86
|
+
const instanceRepository = manager.getRepository(approval_instance_entity_1.ApprovalInstanceEntity);
|
|
87
|
+
const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
|
|
88
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
89
|
+
const instance = await instanceRepository.save(instanceRepository.create({
|
|
90
|
+
completedAt: null,
|
|
91
|
+
formData,
|
|
92
|
+
formDefinitionSnapshot: {
|
|
93
|
+
formDefinitionVersionId: formDefinitionVersion.id,
|
|
94
|
+
schema: formDefinitionVersion.schema,
|
|
95
|
+
uiSchema: formDefinitionVersion.uiSchema,
|
|
96
|
+
version: formDefinitionVersion.version,
|
|
97
|
+
},
|
|
98
|
+
initiatorMemberId: input.initiatorMemberId,
|
|
99
|
+
initiatorMetadataSnapshot,
|
|
100
|
+
startedAt,
|
|
101
|
+
state: workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING,
|
|
102
|
+
templateId: template.id,
|
|
103
|
+
templateVersionId: templateVersion.id,
|
|
104
|
+
title: input.title?.trim() || template.name,
|
|
105
|
+
workflowSnapshot: templateVersion.workflowDefinition,
|
|
106
|
+
}));
|
|
107
|
+
await this.attachmentService.bindFormDataAttachmentsToInstance(manager, {
|
|
108
|
+
formData,
|
|
109
|
+
instanceId: instance.id,
|
|
110
|
+
});
|
|
111
|
+
const token = await tokenRepository.save(tokenRepository.create({
|
|
112
|
+
consumedAt: null,
|
|
113
|
+
currentNodeId: startNode.id,
|
|
114
|
+
instanceId: instance.id,
|
|
115
|
+
parentTokenId: null,
|
|
116
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.ACTIVE,
|
|
117
|
+
}));
|
|
118
|
+
await activityRepository.save([
|
|
119
|
+
activityRepository.create({
|
|
120
|
+
actorMemberId: input.initiatorMemberId,
|
|
121
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.INSTANCE_STARTED,
|
|
122
|
+
instanceId: instance.id,
|
|
123
|
+
nodeId: startNode.id,
|
|
124
|
+
payload: {
|
|
125
|
+
templateId: template.id,
|
|
126
|
+
templateVersionId: templateVersion.id,
|
|
127
|
+
},
|
|
128
|
+
taskId: null,
|
|
129
|
+
}),
|
|
130
|
+
activityRepository.create({
|
|
131
|
+
actorMemberId: null,
|
|
132
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TOKEN_CREATED,
|
|
133
|
+
instanceId: instance.id,
|
|
134
|
+
nodeId: startNode.id,
|
|
135
|
+
payload: {
|
|
136
|
+
tokenId: token.id,
|
|
137
|
+
},
|
|
138
|
+
taskId: null,
|
|
139
|
+
}),
|
|
140
|
+
]);
|
|
141
|
+
await this.processRunningInstance(manager, instance);
|
|
142
|
+
return instance;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async processInstance(instanceId) {
|
|
146
|
+
await this.approvalInstanceRepository.manager.transaction(async (manager) => {
|
|
147
|
+
await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
|
|
148
|
+
instanceId,
|
|
149
|
+
]);
|
|
150
|
+
const instance = await manager
|
|
151
|
+
.getRepository(approval_instance_entity_1.ApprovalInstanceEntity)
|
|
152
|
+
.findOne({ where: { id: instanceId } });
|
|
153
|
+
if (!instance) {
|
|
154
|
+
throw new common_1.NotFoundException(`Approval instance ${instanceId} was not found`);
|
|
155
|
+
}
|
|
156
|
+
await manager.getRepository(activity_log_entity_1.ActivityLogEntity).save(manager.getRepository(activity_log_entity_1.ActivityLogEntity).create({
|
|
157
|
+
actorMemberId: null,
|
|
158
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.ENGINE_PROCESS_REQUESTED,
|
|
159
|
+
instanceId,
|
|
160
|
+
nodeId: null,
|
|
161
|
+
payload: {
|
|
162
|
+
state: instance.state,
|
|
163
|
+
},
|
|
164
|
+
taskId: null,
|
|
165
|
+
}));
|
|
166
|
+
if (instance.state !== workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
await this.processRunningInstance(manager, instance);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async decideTask(input) {
|
|
173
|
+
return this.approvalInstanceRepository.manager.transaction(async (manager) => {
|
|
174
|
+
const taskRepository = manager.getRepository(task_entity_1.TaskEntity);
|
|
175
|
+
const taskCandidateRepository = manager.getRepository(task_candidate_entity_1.TaskCandidateEntity);
|
|
176
|
+
const taskDecisionRepository = manager.getRepository(task_decision_entity_1.TaskDecisionEntity);
|
|
177
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
178
|
+
const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
|
|
179
|
+
const instanceRepository = manager.getRepository(approval_instance_entity_1.ApprovalInstanceEntity);
|
|
180
|
+
const task = await taskRepository.findOne({
|
|
181
|
+
where: { id: input.taskId },
|
|
182
|
+
});
|
|
183
|
+
if (!task) {
|
|
184
|
+
throw new common_1.NotFoundException(`Task ${input.taskId} was not found`);
|
|
185
|
+
}
|
|
186
|
+
await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
|
|
187
|
+
task.instanceId,
|
|
188
|
+
]);
|
|
189
|
+
if (task.status !== workflow_engine_enums_1.TaskStatusEnum.PENDING &&
|
|
190
|
+
task.status !== workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS) {
|
|
191
|
+
throw new common_1.ConflictException(`Task ${task.id} is not pending`);
|
|
192
|
+
}
|
|
193
|
+
const { actorCandidate, taskCandidates } = await this.readTaskActorContext(manager, task, input.decidedByMemberId);
|
|
194
|
+
if (input.action !== workflow_engine_enums_1.TaskDecisionActionEnum.APPROVED &&
|
|
195
|
+
input.action !== workflow_engine_enums_1.TaskDecisionActionEnum.REJECTED &&
|
|
196
|
+
input.action !== workflow_engine_enums_1.TaskDecisionActionEnum.RETURNED &&
|
|
197
|
+
input.action !== workflow_engine_enums_1.TaskDecisionActionEnum.TRANSFERRED) {
|
|
198
|
+
throw new common_1.ConflictException(`Task decision action ${input.action} is not supported yet`);
|
|
199
|
+
}
|
|
200
|
+
const decisionComment = input.comment?.trim() || null;
|
|
201
|
+
if (input.action === workflow_engine_enums_1.TaskDecisionActionEnum.REJECTED &&
|
|
202
|
+
!decisionComment) {
|
|
203
|
+
throw new common_1.BadRequestException('Reject decision comment is required');
|
|
204
|
+
}
|
|
205
|
+
const transferToMemberId = input.transferToMemberId?.trim() || null;
|
|
206
|
+
if (input.action === workflow_engine_enums_1.TaskDecisionActionEnum.TRANSFERRED &&
|
|
207
|
+
!transferToMemberId) {
|
|
208
|
+
throw new common_1.BadRequestException('Transfer target member is required');
|
|
209
|
+
}
|
|
210
|
+
if (input.action === workflow_engine_enums_1.TaskDecisionActionEnum.TRANSFERRED &&
|
|
211
|
+
transferToMemberId === input.decidedByMemberId) {
|
|
212
|
+
throw new common_1.ConflictException('Transfer target must be a different member');
|
|
213
|
+
}
|
|
214
|
+
const instance = await instanceRepository.findOne({
|
|
215
|
+
where: { id: task.instanceId },
|
|
216
|
+
});
|
|
217
|
+
if (!instance) {
|
|
218
|
+
throw new common_1.NotFoundException(`Approval instance ${task.instanceId} was not found`);
|
|
219
|
+
}
|
|
220
|
+
if (instance.state !== workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING) {
|
|
221
|
+
throw new common_1.ConflictException(`Approval instance ${instance.id} is not running`);
|
|
222
|
+
}
|
|
223
|
+
const returnToNodeId = input.action === workflow_engine_enums_1.TaskDecisionActionEnum.RETURNED
|
|
224
|
+
? readReturnTargetNodeId(instance.workflowSnapshot, task.nodeId, input.returnToNodeId)
|
|
225
|
+
: null;
|
|
226
|
+
const decidedAt = new Date();
|
|
227
|
+
const claimedTask = await taskRepository.save({
|
|
228
|
+
...task,
|
|
229
|
+
assigneeMemberId: task.assigneeMemberId ?? input.decidedByMemberId,
|
|
230
|
+
openedAt: task.openedAt ?? decidedAt,
|
|
231
|
+
originalAssigneeMemberId: task.originalAssigneeMemberId ??
|
|
232
|
+
actorCandidate?.originalMemberId ??
|
|
233
|
+
input.decidedByMemberId,
|
|
234
|
+
status: workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS,
|
|
235
|
+
});
|
|
236
|
+
const claimedCandidate = actorCandidate
|
|
237
|
+
? await taskCandidateRepository.save({
|
|
238
|
+
...actorCandidate,
|
|
239
|
+
claimedAt: actorCandidate.claimedAt ?? decidedAt,
|
|
240
|
+
status: workflow_engine_enums_1.TaskCandidateStatusEnum.CLAIMED,
|
|
241
|
+
})
|
|
242
|
+
: null;
|
|
243
|
+
const signature = await this.signatureService.signTaskDecision(manager, {
|
|
244
|
+
action: input.action,
|
|
245
|
+
comment: decisionComment,
|
|
246
|
+
decidedAt,
|
|
247
|
+
instance,
|
|
248
|
+
returnToNodeId,
|
|
249
|
+
signerMemberId: input.decidedByMemberId,
|
|
250
|
+
task: claimedTask,
|
|
251
|
+
transferToMemberId,
|
|
252
|
+
});
|
|
253
|
+
const decision = await taskDecisionRepository.save(taskDecisionRepository.create({
|
|
254
|
+
action: input.action,
|
|
255
|
+
comment: decisionComment,
|
|
256
|
+
decidedAt,
|
|
257
|
+
decidedByMemberId: input.decidedByMemberId,
|
|
258
|
+
returnToNodeId,
|
|
259
|
+
signatureId: signature.id,
|
|
260
|
+
taskId: task.id,
|
|
261
|
+
transferToMemberId,
|
|
262
|
+
}));
|
|
263
|
+
if (input.action === workflow_engine_enums_1.TaskDecisionActionEnum.TRANSFERRED) {
|
|
264
|
+
await this.transferTask(manager, instance, claimedTask, decision, signature, transferToMemberId, decisionComment, decidedAt, claimedCandidate);
|
|
265
|
+
await this.notificationService.resolveTaskNotifications({
|
|
266
|
+
actingMemberId: input.decidedByMemberId,
|
|
267
|
+
manager,
|
|
268
|
+
resolution: notification_enums_1.NotificationResolutionEnum.TRANSFERRED,
|
|
269
|
+
supersedeOthers: true,
|
|
270
|
+
taskId: claimedTask.id,
|
|
271
|
+
});
|
|
272
|
+
return decision;
|
|
273
|
+
}
|
|
274
|
+
const nextCandidateRows = await this.markTaskCandidateDecision({
|
|
275
|
+
action: input.action,
|
|
276
|
+
candidate: claimedCandidate,
|
|
277
|
+
candidates: taskCandidates,
|
|
278
|
+
decidedAt,
|
|
279
|
+
manager,
|
|
280
|
+
task: claimedTask,
|
|
281
|
+
});
|
|
282
|
+
const shouldCompleteTask = shouldCompleteTaskAfterDecision({
|
|
283
|
+
action: input.action,
|
|
284
|
+
candidates: nextCandidateRows,
|
|
285
|
+
decisionPolicy: readDecisionPolicySnapshot(claimedTask),
|
|
286
|
+
});
|
|
287
|
+
const completedTask = shouldCompleteTask
|
|
288
|
+
? await taskRepository.save({
|
|
289
|
+
...claimedTask,
|
|
290
|
+
completedAt: decidedAt,
|
|
291
|
+
status: workflow_engine_enums_1.TaskStatusEnum.COMPLETED,
|
|
292
|
+
})
|
|
293
|
+
: await taskRepository.save({
|
|
294
|
+
...claimedTask,
|
|
295
|
+
status: workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS,
|
|
296
|
+
});
|
|
297
|
+
await activityRepository.save(activityRepository.create({
|
|
298
|
+
actorMemberId: input.decidedByMemberId,
|
|
299
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TASK_DECIDED,
|
|
300
|
+
instanceId: instance.id,
|
|
301
|
+
nodeId: claimedTask.nodeId,
|
|
302
|
+
payload: {
|
|
303
|
+
action: input.action,
|
|
304
|
+
comment: decisionComment,
|
|
305
|
+
decisionId: decision.id,
|
|
306
|
+
signatureId: signature.id,
|
|
307
|
+
signedPayloadHash: signature.signedPayloadHash,
|
|
308
|
+
},
|
|
309
|
+
taskId: completedTask.id,
|
|
310
|
+
}));
|
|
311
|
+
// Resolve the recipient's "待簽" notification so its inline 同意/拒絕
|
|
312
|
+
// actions disappear once the task is decided. When the task has ended
|
|
313
|
+
// (rejected / returned, or an approval that completed it), every other
|
|
314
|
+
// candidate's notification is superseded too; a non-completing approval
|
|
315
|
+
// on a multi-approver task leaves the remaining approvers' open.
|
|
316
|
+
const taskEnded = input.action !== workflow_engine_enums_1.TaskDecisionActionEnum.APPROVED || shouldCompleteTask;
|
|
317
|
+
await this.notificationService.resolveTaskNotifications({
|
|
318
|
+
actingMemberId: input.decidedByMemberId,
|
|
319
|
+
manager,
|
|
320
|
+
resolution: mapDecisionToResolution(input.action),
|
|
321
|
+
supersedeOthers: taskEnded,
|
|
322
|
+
taskId: completedTask.id,
|
|
323
|
+
});
|
|
324
|
+
if (input.action === workflow_engine_enums_1.TaskDecisionActionEnum.APPROVED &&
|
|
325
|
+
shouldCompleteTask) {
|
|
326
|
+
// Ad-hoc gate: the token may only advance once every task bound to
|
|
327
|
+
// this token+node (the original task plus any ad-hoc countersign /
|
|
328
|
+
// pre-approval tasks) has reached a terminal state. Nodes without
|
|
329
|
+
// ad-hoc tasks keep the original single-task behaviour.
|
|
330
|
+
if (await this.hasOpenTasksForTokenNode(manager, claimedTask.tokenId, claimedTask.nodeId)) {
|
|
331
|
+
return decision;
|
|
332
|
+
}
|
|
333
|
+
const token = await tokenRepository.findOne({
|
|
334
|
+
where: { id: claimedTask.tokenId },
|
|
335
|
+
});
|
|
336
|
+
if (!token) {
|
|
337
|
+
throw new common_1.NotFoundException(`Workflow token ${claimedTask.tokenId} was not found`);
|
|
338
|
+
}
|
|
339
|
+
if (token.status !== workflow_engine_enums_1.WorkflowTokenStatusEnum.WAITING) {
|
|
340
|
+
throw new common_1.ConflictException(`Workflow token ${token.id} is not waiting for a task decision`);
|
|
341
|
+
}
|
|
342
|
+
const activeToken = await tokenRepository.save({
|
|
343
|
+
...token,
|
|
344
|
+
consumedAt: null,
|
|
345
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.ACTIVE,
|
|
346
|
+
});
|
|
347
|
+
await this.dispatchAdhocStageNotifications(manager, instance, claimedTask.nodeId, 'APPROVED');
|
|
348
|
+
await this.advanceTokenToOutgoingNodes(manager, instance, activeToken, readWorkflowNodeOrThrow(instance.workflowSnapshot, claimedTask.nodeId));
|
|
349
|
+
await this.processRunningInstance(manager, instance);
|
|
350
|
+
return decision;
|
|
351
|
+
}
|
|
352
|
+
if (input.action === workflow_engine_enums_1.TaskDecisionActionEnum.APPROVED) {
|
|
353
|
+
return decision;
|
|
354
|
+
}
|
|
355
|
+
if (input.action === workflow_engine_enums_1.TaskDecisionActionEnum.RETURNED) {
|
|
356
|
+
await this.returnInstanceToNode(manager, instance, completedTask, returnToNodeId, decidedAt);
|
|
357
|
+
return decision;
|
|
358
|
+
}
|
|
359
|
+
if (await this.handleAdhocPreApprovalRejection(manager, instance, completedTask, decidedAt, decisionComment)) {
|
|
360
|
+
return decision;
|
|
361
|
+
}
|
|
362
|
+
await this.rejectInstance(manager, instance, completedTask, decidedAt);
|
|
363
|
+
return decision;
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
async cancelApprovalInstance(input) {
|
|
367
|
+
return this.approvalInstanceRepository.manager.transaction(async (manager) => {
|
|
368
|
+
await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
|
|
369
|
+
input.instanceId,
|
|
370
|
+
]);
|
|
371
|
+
const instanceRepository = manager.getRepository(approval_instance_entity_1.ApprovalInstanceEntity);
|
|
372
|
+
const instance = await instanceRepository.findOne({
|
|
373
|
+
where: { id: input.instanceId },
|
|
374
|
+
});
|
|
375
|
+
if (!instance) {
|
|
376
|
+
throw new common_1.NotFoundException(`Approval instance ${input.instanceId} was not found`);
|
|
377
|
+
}
|
|
378
|
+
if (instance.initiatorMemberId !== input.cancelledByMemberId) {
|
|
379
|
+
throw new common_1.ConflictException(`Approval instance ${instance.id} can only be cancelled by its initiator`);
|
|
380
|
+
}
|
|
381
|
+
if (instance.state !== workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING &&
|
|
382
|
+
instance.state !== workflow_engine_enums_1.ApprovalInstanceStateEnum.RETURNED) {
|
|
383
|
+
throw new common_1.ConflictException(`Approval instance ${instance.id} is not cancellable`);
|
|
384
|
+
}
|
|
385
|
+
const cancelledAt = new Date();
|
|
386
|
+
await this.consumeOpenRuntimeState(manager, instance, cancelledAt, workflow_engine_enums_1.TaskStatusEnum.CANCELLED);
|
|
387
|
+
await this.notificationService.supersedeInstanceTaskNotifications({
|
|
388
|
+
instanceId: instance.id,
|
|
389
|
+
manager,
|
|
390
|
+
});
|
|
391
|
+
const cancelledInstance = await instanceRepository.save({
|
|
392
|
+
...instance,
|
|
393
|
+
completedAt: cancelledAt,
|
|
394
|
+
state: workflow_engine_enums_1.ApprovalInstanceStateEnum.CANCELLED,
|
|
395
|
+
});
|
|
396
|
+
await this.dispatchAdhocCompletionNotifications(manager, cancelledInstance, workflow_engine_enums_1.ApprovalInstanceStateEnum.CANCELLED);
|
|
397
|
+
await this.cancelPendingAdhocDirectives(manager, instance.id, null);
|
|
398
|
+
await manager.getRepository(activity_log_entity_1.ActivityLogEntity).save(manager.getRepository(activity_log_entity_1.ActivityLogEntity).create({
|
|
399
|
+
actorMemberId: input.cancelledByMemberId,
|
|
400
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.INSTANCE_CANCELLED,
|
|
401
|
+
instanceId: instance.id,
|
|
402
|
+
nodeId: null,
|
|
403
|
+
payload: {
|
|
404
|
+
comment: input.comment?.trim() || null,
|
|
405
|
+
},
|
|
406
|
+
taskId: null,
|
|
407
|
+
}));
|
|
408
|
+
return cancelledInstance;
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
async resubmitApprovalInstance(input) {
|
|
412
|
+
const formData = parseJsonObject(input.formDataJson, 'formDataJson');
|
|
413
|
+
return this.approvalInstanceRepository.manager.transaction(async (manager) => {
|
|
414
|
+
await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
|
|
415
|
+
input.instanceId,
|
|
416
|
+
]);
|
|
417
|
+
const instanceRepository = manager.getRepository(approval_instance_entity_1.ApprovalInstanceEntity);
|
|
418
|
+
const instance = await instanceRepository.findOne({
|
|
419
|
+
where: { id: input.instanceId },
|
|
420
|
+
});
|
|
421
|
+
if (!instance) {
|
|
422
|
+
throw new common_1.NotFoundException(`Approval instance ${input.instanceId} was not found`);
|
|
423
|
+
}
|
|
424
|
+
if (instance.initiatorMemberId !== input.initiatorMemberId) {
|
|
425
|
+
throw new common_1.ConflictException(`Approval instance ${instance.id} can only be resubmitted by its initiator`);
|
|
426
|
+
}
|
|
427
|
+
if (instance.state !== workflow_engine_enums_1.ApprovalInstanceStateEnum.RETURNED) {
|
|
428
|
+
throw new common_1.ConflictException(`Approval instance ${instance.id} is not returned`);
|
|
429
|
+
}
|
|
430
|
+
validateSubmittedFormData(readFormDefinitionSnapshotSchema(instance.formDefinitionSnapshot), formData);
|
|
431
|
+
const resubmittedInstance = await instanceRepository.save({
|
|
432
|
+
...instance,
|
|
433
|
+
completedAt: null,
|
|
434
|
+
formData,
|
|
435
|
+
state: workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING,
|
|
436
|
+
title: input.title?.trim() || instance.title,
|
|
437
|
+
});
|
|
438
|
+
await this.attachmentService.bindFormDataAttachmentsToInstance(manager, {
|
|
439
|
+
formData,
|
|
440
|
+
instanceId: instance.id,
|
|
441
|
+
});
|
|
442
|
+
const returnedContext = await this.readLatestReturnedInstanceContext(manager, instance.id);
|
|
443
|
+
const runtimeInstance = returnedContext?.resubmitStrategy === 'FROM_RETURN_POINT'
|
|
444
|
+
? await this.resumeReturnedInstanceFromReturnPoint(manager, resubmittedInstance, returnedContext, new Date())
|
|
445
|
+
: resubmittedInstance;
|
|
446
|
+
await manager.getRepository(activity_log_entity_1.ActivityLogEntity).save(manager.getRepository(activity_log_entity_1.ActivityLogEntity).create({
|
|
447
|
+
actorMemberId: input.initiatorMemberId,
|
|
448
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.INSTANCE_RESUBMITTED,
|
|
449
|
+
instanceId: instance.id,
|
|
450
|
+
nodeId: null,
|
|
451
|
+
payload: {
|
|
452
|
+
resubmitStrategy: returnedContext?.resubmitStrategy ?? 'RESTART',
|
|
453
|
+
},
|
|
454
|
+
taskId: null,
|
|
455
|
+
}));
|
|
456
|
+
await this.processRunningInstance(manager, runtimeInstance);
|
|
457
|
+
return runtimeInstance;
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
async requestAdhocCountersign({ comment, requestedByMemberId, target, taskId, }) {
|
|
461
|
+
return this.approvalInstanceRepository.manager.transaction(async (manager) => {
|
|
462
|
+
const { instance, node, task } = await this.loadAdhocOperationContext(manager, taskId, requestedByMemberId, { requireAllowAddSigner: true });
|
|
463
|
+
const targetValue = buildAdhocTargetValue(target);
|
|
464
|
+
// Rejects WEBHOOK targets — countersigners must be members.
|
|
465
|
+
buildAdhocApproverResolver(targetValue);
|
|
466
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
467
|
+
const directive = await directiveRepository.save(directiveRepository.create({
|
|
468
|
+
channels: null,
|
|
469
|
+
comment: comment?.trim() || null,
|
|
470
|
+
consumedAt: null,
|
|
471
|
+
createdByMemberId: requestedByMemberId,
|
|
472
|
+
instanceId: instance.id,
|
|
473
|
+
onReject: null,
|
|
474
|
+
originNodeId: task.nodeId,
|
|
475
|
+
originTaskId: task.id,
|
|
476
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.PENDING,
|
|
477
|
+
targetKind: targetValue.kind,
|
|
478
|
+
targetValue: { ...targetValue },
|
|
479
|
+
type: adhoc_enums_1.AdhocDirectiveTypeEnum.COUNTERSIGN,
|
|
480
|
+
}));
|
|
481
|
+
await this.recordAdhocDirectiveActivity(manager, directive, workflow_engine_enums_1.ActivityLogEventTypeEnum.ADHOC_DIRECTIVE_CREATED, requestedByMemberId, { nodeLabel: node.data.label });
|
|
482
|
+
return directive;
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
async requestAdhocPreApproval({ comment, onReject, requestedByMemberId, target, taskId, }) {
|
|
486
|
+
return this.approvalInstanceRepository.manager.transaction(async (manager) => {
|
|
487
|
+
const { instance, node, task } = await this.loadAdhocOperationContext(manager, taskId, requestedByMemberId, { requireAllowAddSigner: true });
|
|
488
|
+
const targetValue = buildAdhocTargetValue(target);
|
|
489
|
+
// Rejects WEBHOOK targets — pre-approvers must be members.
|
|
490
|
+
buildAdhocApproverResolver(targetValue);
|
|
491
|
+
const now = new Date();
|
|
492
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
493
|
+
const directive = await directiveRepository.save(directiveRepository.create({
|
|
494
|
+
channels: null,
|
|
495
|
+
comment: comment?.trim() || null,
|
|
496
|
+
consumedAt: now,
|
|
497
|
+
createdByMemberId: requestedByMemberId,
|
|
498
|
+
instanceId: instance.id,
|
|
499
|
+
onReject,
|
|
500
|
+
originNodeId: task.nodeId,
|
|
501
|
+
originTaskId: task.id,
|
|
502
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.CONSUMED,
|
|
503
|
+
targetKind: targetValue.kind,
|
|
504
|
+
targetValue: { ...targetValue },
|
|
505
|
+
type: adhoc_enums_1.AdhocDirectiveTypeEnum.PRE_APPROVAL,
|
|
506
|
+
}));
|
|
507
|
+
const adhocTask = await this.createAdhocTaskForDirective(manager, instance, task.tokenId, node, directive, now);
|
|
508
|
+
await this.recordAdhocDirectiveActivity(manager, directive, workflow_engine_enums_1.ActivityLogEventTypeEnum.ADHOC_DIRECTIVE_CREATED, requestedByMemberId, { adhocTaskId: adhocTask.id, nodeLabel: node.data.label, onReject });
|
|
509
|
+
return adhocTask;
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
async configureAdhocNotification({ channels, requestedByMemberId, target, taskId, type, }) {
|
|
513
|
+
return this.approvalInstanceRepository.manager.transaction(async (manager) => {
|
|
514
|
+
const { instance, node, task } = await this.loadAdhocOperationContext(manager, taskId, requestedByMemberId, { requireAllowAddSigner: false });
|
|
515
|
+
const targetValue = buildAdhocTargetValue(target);
|
|
516
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
517
|
+
const directive = await directiveRepository.save(directiveRepository.create({
|
|
518
|
+
channels: channels?.length ? [...channels] : null,
|
|
519
|
+
comment: null,
|
|
520
|
+
consumedAt: null,
|
|
521
|
+
createdByMemberId: requestedByMemberId,
|
|
522
|
+
instanceId: instance.id,
|
|
523
|
+
onReject: null,
|
|
524
|
+
originNodeId: task.nodeId,
|
|
525
|
+
originTaskId: task.id,
|
|
526
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.PENDING,
|
|
527
|
+
targetKind: targetValue.kind,
|
|
528
|
+
targetValue: { ...targetValue },
|
|
529
|
+
type,
|
|
530
|
+
}));
|
|
531
|
+
await this.recordAdhocDirectiveActivity(manager, directive, workflow_engine_enums_1.ActivityLogEventTypeEnum.ADHOC_DIRECTIVE_CREATED, requestedByMemberId, { nodeLabel: node.data.label });
|
|
532
|
+
return directive;
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
async cancelAdhocDirective({ cancelledByMemberId, directiveId, }) {
|
|
536
|
+
return this.approvalInstanceRepository.manager.transaction(async (manager) => {
|
|
537
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
538
|
+
const directive = await directiveRepository.findOne({
|
|
539
|
+
where: { id: directiveId },
|
|
540
|
+
});
|
|
541
|
+
if (!directive) {
|
|
542
|
+
throw new common_1.NotFoundException(`Ad-hoc directive ${directiveId} was not found`);
|
|
543
|
+
}
|
|
544
|
+
await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
|
|
545
|
+
directive.instanceId,
|
|
546
|
+
]);
|
|
547
|
+
if (directive.createdByMemberId !== cancelledByMemberId) {
|
|
548
|
+
throw new common_1.ConflictException(`Ad-hoc directive ${directive.id} can only be cancelled by its creator`);
|
|
549
|
+
}
|
|
550
|
+
if (directive.status !== adhoc_enums_1.AdhocDirectiveStatusEnum.PENDING) {
|
|
551
|
+
throw new common_1.ConflictException(`Ad-hoc directive ${directive.id} is not pending`);
|
|
552
|
+
}
|
|
553
|
+
const cancelledDirective = await directiveRepository.save({
|
|
554
|
+
...directive,
|
|
555
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.CANCELLED,
|
|
556
|
+
});
|
|
557
|
+
await this.recordAdhocDirectiveActivity(manager, cancelledDirective, workflow_engine_enums_1.ActivityLogEventTypeEnum.ADHOC_DIRECTIVE_CANCELLED, cancelledByMemberId);
|
|
558
|
+
return cancelledDirective;
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
async listAdhocDirectives(instanceId, scope) {
|
|
562
|
+
await this.getApprovalInstance(instanceId, scope);
|
|
563
|
+
return this.adhocDirectiveRepository.find({
|
|
564
|
+
order: { createdAt: 'ASC' },
|
|
565
|
+
where: { instanceId },
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
async readTaskActorContext(manager, task, actingMemberId) {
|
|
569
|
+
const taskCandidates = await manager
|
|
570
|
+
.getRepository(task_candidate_entity_1.TaskCandidateEntity)
|
|
571
|
+
.find({ where: { taskId: task.id } });
|
|
572
|
+
const actorCandidate = taskCandidates.find((candidate) => candidate.memberId === actingMemberId) ?? null;
|
|
573
|
+
const isDirectAssignee = task.assigneeMemberId === actingMemberId;
|
|
574
|
+
if (!isDirectAssignee && !actorCandidate) {
|
|
575
|
+
throw new common_1.ConflictException(`Task ${task.id} is assigned to another member`);
|
|
576
|
+
}
|
|
577
|
+
if (actorCandidate &&
|
|
578
|
+
actorCandidate.status !== workflow_engine_enums_1.TaskCandidateStatusEnum.PENDING &&
|
|
579
|
+
actorCandidate.status !== workflow_engine_enums_1.TaskCandidateStatusEnum.CLAIMED) {
|
|
580
|
+
throw new common_1.ConflictException(`Task ${task.id} was already decided by this member`);
|
|
581
|
+
}
|
|
582
|
+
return { actorCandidate, taskCandidates };
|
|
583
|
+
}
|
|
584
|
+
async loadAdhocOperationContext(manager, taskId, actingMemberId, options) {
|
|
585
|
+
const task = await manager
|
|
586
|
+
.getRepository(task_entity_1.TaskEntity)
|
|
587
|
+
.findOne({ where: { id: taskId } });
|
|
588
|
+
if (!task) {
|
|
589
|
+
throw new common_1.NotFoundException(`Task ${taskId} was not found`);
|
|
590
|
+
}
|
|
591
|
+
await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
|
|
592
|
+
task.instanceId,
|
|
593
|
+
]);
|
|
594
|
+
if (task.status !== workflow_engine_enums_1.TaskStatusEnum.PENDING &&
|
|
595
|
+
task.status !== workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS) {
|
|
596
|
+
throw new common_1.ConflictException(`Task ${task.id} is not pending`);
|
|
597
|
+
}
|
|
598
|
+
await this.readTaskActorContext(manager, task, actingMemberId);
|
|
599
|
+
const instance = await manager
|
|
600
|
+
.getRepository(approval_instance_entity_1.ApprovalInstanceEntity)
|
|
601
|
+
.findOne({ where: { id: task.instanceId } });
|
|
602
|
+
if (!instance) {
|
|
603
|
+
throw new common_1.NotFoundException(`Approval instance ${task.instanceId} was not found`);
|
|
604
|
+
}
|
|
605
|
+
if (instance.state !== workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING) {
|
|
606
|
+
throw new common_1.ConflictException(`Approval instance ${instance.id} is not running`);
|
|
607
|
+
}
|
|
608
|
+
const node = readWorkflowNodeOrThrow(instance.workflowSnapshot, task.nodeId);
|
|
609
|
+
if (node.type !== 'userTask') {
|
|
610
|
+
throw new common_1.ConflictException(`Task ${task.id} is not bound to a user task node`);
|
|
611
|
+
}
|
|
612
|
+
if (options.requireAllowAddSigner && !node.data.allowAddSigner) {
|
|
613
|
+
throw new common_1.ForbiddenException(`簽核節點「${node.data.label}」does not allow ad-hoc signers`);
|
|
614
|
+
}
|
|
615
|
+
return { instance, node, task };
|
|
616
|
+
}
|
|
617
|
+
async recordAdhocDirectiveActivity(manager, directive, eventType, actorMemberId, extraPayload = {}) {
|
|
618
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
619
|
+
await activityRepository.save(activityRepository.create({
|
|
620
|
+
actorMemberId,
|
|
621
|
+
eventType,
|
|
622
|
+
instanceId: directive.instanceId,
|
|
623
|
+
nodeId: directive.originNodeId,
|
|
624
|
+
payload: {
|
|
625
|
+
...extraPayload,
|
|
626
|
+
directiveId: directive.id,
|
|
627
|
+
directiveStatus: directive.status,
|
|
628
|
+
directiveType: directive.type,
|
|
629
|
+
targetKind: directive.targetKind,
|
|
630
|
+
},
|
|
631
|
+
taskId: directive.originTaskId,
|
|
632
|
+
}));
|
|
633
|
+
}
|
|
634
|
+
async createAdhocTaskForDirective(manager, instance, tokenId, node, directive, now) {
|
|
635
|
+
const taskRepository = manager.getRepository(task_entity_1.TaskEntity);
|
|
636
|
+
const taskCandidateRepository = manager.getRepository(task_candidate_entity_1.TaskCandidateEntity);
|
|
637
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
638
|
+
const label = directive.type === adhoc_enums_1.AdhocDirectiveTypeEnum.COUNTERSIGN
|
|
639
|
+
? '臨時會簽'
|
|
640
|
+
: '臨時加簽';
|
|
641
|
+
const resolvedCandidates = await this.resolveApproverResolver(manager, instance, buildAdhocApproverResolver(readAdhocTargetValue(directive)), `${label}「${node.data.label}」`);
|
|
642
|
+
const candidates = await this.applyDelegationToResolvedCandidates(instance, node.id, resolvedCandidates);
|
|
643
|
+
if (candidates.length === 0) {
|
|
644
|
+
throw new common_1.ConflictException(`${label}「${node.data.label}」 did not resolve to any member id`);
|
|
645
|
+
}
|
|
646
|
+
const primaryCandidate = candidates[0];
|
|
647
|
+
const task = await taskRepository.save(taskRepository.create({
|
|
648
|
+
adhocDirectiveId: directive.id,
|
|
649
|
+
adhocOriginTaskId: directive.originTaskId,
|
|
650
|
+
adhocType: directive.type,
|
|
651
|
+
assigneeMemberId: candidates.length === 1 ? primaryCandidate.memberId : null,
|
|
652
|
+
assignmentType: candidates.length === 1
|
|
653
|
+
? workflow_engine_enums_1.TaskAssignmentTypeEnum.DIRECT_MEMBER
|
|
654
|
+
: workflow_engine_enums_1.TaskAssignmentTypeEnum.CANDIDATE_GROUP,
|
|
655
|
+
completedAt: null,
|
|
656
|
+
createdAt: now,
|
|
657
|
+
decisionPolicySnapshot: { type: 'SINGLE' },
|
|
658
|
+
delegationChain: candidates.length === 1 ? primaryCandidate.delegationChain : [],
|
|
659
|
+
instanceId: instance.id,
|
|
660
|
+
isAdhoc: true,
|
|
661
|
+
nodeId: node.id,
|
|
662
|
+
openedAt: null,
|
|
663
|
+
originalAssigneeMemberId: candidates.length === 1 ? primaryCandidate.originalMemberId : null,
|
|
664
|
+
slaDueAt: null,
|
|
665
|
+
status: workflow_engine_enums_1.TaskStatusEnum.PENDING,
|
|
666
|
+
tokenId,
|
|
667
|
+
}));
|
|
668
|
+
const savedCandidates = await taskCandidateRepository.save(candidates.map((candidate) => taskCandidateRepository.create({
|
|
669
|
+
claimedAt: null,
|
|
670
|
+
createdAt: now,
|
|
671
|
+
decidedAt: null,
|
|
672
|
+
delegationChain: candidate.delegationChain,
|
|
673
|
+
memberId: candidate.memberId,
|
|
674
|
+
originalMemberId: candidate.originalMemberId,
|
|
675
|
+
sourceType: candidate.sourceType,
|
|
676
|
+
status: workflow_engine_enums_1.TaskCandidateStatusEnum.PENDING,
|
|
677
|
+
taskId: task.id,
|
|
678
|
+
})));
|
|
679
|
+
task.candidateMemberIds = savedCandidates.map((candidate) => candidate.memberId);
|
|
680
|
+
await activityRepository.save(activityRepository.create({
|
|
681
|
+
actorMemberId: directive.createdByMemberId,
|
|
682
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TASK_CREATED,
|
|
683
|
+
instanceId: instance.id,
|
|
684
|
+
nodeId: node.id,
|
|
685
|
+
payload: {
|
|
686
|
+
adhocDirectiveId: directive.id,
|
|
687
|
+
adhocType: directive.type,
|
|
688
|
+
assigneeMemberId: task.assigneeMemberId,
|
|
689
|
+
assignmentType: task.assignmentType,
|
|
690
|
+
candidateMemberIds: savedCandidates.map((candidate) => candidate.memberId),
|
|
691
|
+
originTaskId: directive.originTaskId,
|
|
692
|
+
tokenId,
|
|
693
|
+
},
|
|
694
|
+
taskId: task.id,
|
|
695
|
+
}));
|
|
696
|
+
await savedCandidates.reduce(async (previous, candidate) => {
|
|
697
|
+
await previous;
|
|
698
|
+
await this.notificationService.createTaskAssignedNotification({
|
|
699
|
+
instance,
|
|
700
|
+
manager,
|
|
701
|
+
node,
|
|
702
|
+
task: Object.assign(new task_entity_1.TaskEntity(), task, {
|
|
703
|
+
assigneeMemberId: candidate.memberId,
|
|
704
|
+
delegationChain: candidate.delegationChain,
|
|
705
|
+
originalAssigneeMemberId: candidate.originalMemberId,
|
|
706
|
+
}),
|
|
707
|
+
});
|
|
708
|
+
}, Promise.resolve());
|
|
709
|
+
return task;
|
|
710
|
+
}
|
|
711
|
+
async spawnCountersignTasksForNode(manager, instance, token, node, now) {
|
|
712
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
713
|
+
const directives = await directiveRepository.find({
|
|
714
|
+
order: { createdAt: 'ASC' },
|
|
715
|
+
where: {
|
|
716
|
+
instanceId: instance.id,
|
|
717
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.PENDING,
|
|
718
|
+
type: adhoc_enums_1.AdhocDirectiveTypeEnum.COUNTERSIGN,
|
|
719
|
+
},
|
|
720
|
+
});
|
|
721
|
+
if (directives.length === 0) {
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
await directives.reduce(async (previous, directive) => {
|
|
725
|
+
await previous;
|
|
726
|
+
try {
|
|
727
|
+
await this.createAdhocTaskForDirective(manager, instance, token.id, node, directive, now);
|
|
728
|
+
await directiveRepository.save({
|
|
729
|
+
...directive,
|
|
730
|
+
consumedAt: now,
|
|
731
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.CONSUMED,
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
catch (error) {
|
|
735
|
+
// A countersign target that no longer resolves must not block the
|
|
736
|
+
// main flow — cancel the directive and record the failure.
|
|
737
|
+
await directiveRepository.save({
|
|
738
|
+
...directive,
|
|
739
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.CANCELLED,
|
|
740
|
+
});
|
|
741
|
+
await this.recordAdhocDirectiveActivity(manager, directive, workflow_engine_enums_1.ActivityLogEventTypeEnum.ADHOC_DIRECTIVE_CANCELLED, null, {
|
|
742
|
+
error: error instanceof Error
|
|
743
|
+
? error.message
|
|
744
|
+
: 'Countersign target resolution failed',
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
}, Promise.resolve());
|
|
748
|
+
}
|
|
749
|
+
async hasOpenTasksForTokenNode(manager, tokenId, nodeId) {
|
|
750
|
+
const tasks = await manager.getRepository(task_entity_1.TaskEntity).find({
|
|
751
|
+
where: { nodeId, tokenId },
|
|
752
|
+
});
|
|
753
|
+
return tasks.some((task) => task.tokenId === tokenId &&
|
|
754
|
+
task.nodeId === nodeId &&
|
|
755
|
+
(task.status === workflow_engine_enums_1.TaskStatusEnum.PENDING ||
|
|
756
|
+
task.status === workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS));
|
|
757
|
+
}
|
|
758
|
+
async handleAdhocPreApprovalRejection(manager, instance, task, decidedAt, decisionComment) {
|
|
759
|
+
if (!task.isAdhoc ||
|
|
760
|
+
task.adhocType !== adhoc_enums_1.AdhocDirectiveTypeEnum.PRE_APPROVAL ||
|
|
761
|
+
!task.adhocDirectiveId) {
|
|
762
|
+
return false;
|
|
763
|
+
}
|
|
764
|
+
const directive = await manager
|
|
765
|
+
.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity)
|
|
766
|
+
.findOne({ where: { id: task.adhocDirectiveId } });
|
|
767
|
+
if (directive?.onReject !==
|
|
768
|
+
adhoc_enums_1.AdhocPreApprovalRejectBehaviorEnum.RETURN_TO_ORIGIN) {
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
const taskRepository = manager.getRepository(task_entity_1.TaskEntity);
|
|
772
|
+
const taskCandidateRepository = manager.getRepository(task_candidate_entity_1.TaskCandidateEntity);
|
|
773
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
774
|
+
const originTask = task.adhocOriginTaskId
|
|
775
|
+
? await taskRepository.findOne({ where: { id: task.adhocOriginTaskId } })
|
|
776
|
+
: null;
|
|
777
|
+
const originTaskIsOpen = originTask?.status === workflow_engine_enums_1.TaskStatusEnum.PENDING ||
|
|
778
|
+
originTask?.status === workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS;
|
|
779
|
+
// Safety: when there is nobody to hand the decision back to (origin task
|
|
780
|
+
// missing, or already closed without a resolvable assignee), fall back to
|
|
781
|
+
// the default rejection path instead of leaving the token stuck WAITING
|
|
782
|
+
// with no open task.
|
|
783
|
+
if (!originTask || (!originTaskIsOpen && !originTask.assigneeMemberId)) {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
const node = readWorkflowNodeOrThrow(instance.workflowSnapshot, task.nodeId);
|
|
787
|
+
if (!originTaskIsOpen && originTask.assigneeMemberId) {
|
|
788
|
+
// The origin approver already decided — reopen a fresh decision task so
|
|
789
|
+
// the stage can be re-decided after the rejected pre-approval.
|
|
790
|
+
const reopenedTask = await taskRepository.save(taskRepository.create({
|
|
791
|
+
assigneeMemberId: originTask.assigneeMemberId,
|
|
792
|
+
assignmentType: workflow_engine_enums_1.TaskAssignmentTypeEnum.DIRECT_MEMBER,
|
|
793
|
+
completedAt: null,
|
|
794
|
+
createdAt: decidedAt,
|
|
795
|
+
decisionPolicySnapshot: { type: 'SINGLE' },
|
|
796
|
+
delegationChain: originTask.delegationChain,
|
|
797
|
+
instanceId: instance.id,
|
|
798
|
+
nodeId: originTask.nodeId,
|
|
799
|
+
openedAt: null,
|
|
800
|
+
originalAssigneeMemberId: originTask.originalAssigneeMemberId,
|
|
801
|
+
slaDueAt: null,
|
|
802
|
+
status: workflow_engine_enums_1.TaskStatusEnum.PENDING,
|
|
803
|
+
tokenId: originTask.tokenId,
|
|
804
|
+
}));
|
|
805
|
+
await taskCandidateRepository.save(taskCandidateRepository.create({
|
|
806
|
+
claimedAt: null,
|
|
807
|
+
createdAt: decidedAt,
|
|
808
|
+
decidedAt: null,
|
|
809
|
+
delegationChain: originTask.delegationChain,
|
|
810
|
+
memberId: originTask.assigneeMemberId,
|
|
811
|
+
originalMemberId: originTask.originalAssigneeMemberId ?? originTask.assigneeMemberId,
|
|
812
|
+
sourceType: 'DIRECT',
|
|
813
|
+
status: workflow_engine_enums_1.TaskCandidateStatusEnum.PENDING,
|
|
814
|
+
taskId: reopenedTask.id,
|
|
815
|
+
}));
|
|
816
|
+
await activityRepository.save(activityRepository.create({
|
|
817
|
+
actorMemberId: task.assigneeMemberId,
|
|
818
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TASK_CREATED,
|
|
819
|
+
instanceId: instance.id,
|
|
820
|
+
nodeId: originTask.nodeId,
|
|
821
|
+
payload: {
|
|
822
|
+
adhocDirectiveId: directive.id,
|
|
823
|
+
assigneeMemberId: originTask.assigneeMemberId,
|
|
824
|
+
reopenedFromTaskId: originTask.id,
|
|
825
|
+
rejectedAdhocTaskId: task.id,
|
|
826
|
+
tokenId: originTask.tokenId,
|
|
827
|
+
},
|
|
828
|
+
taskId: reopenedTask.id,
|
|
829
|
+
}));
|
|
830
|
+
if (node.type === 'userTask') {
|
|
831
|
+
await this.notificationService.createTaskAssignedNotification({
|
|
832
|
+
instance,
|
|
833
|
+
manager,
|
|
834
|
+
node,
|
|
835
|
+
task: reopenedTask,
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
const recipientMemberIds = await this.readOpenTaskRecipientMemberIds(manager, originTask);
|
|
840
|
+
if (recipientMemberIds.length > 0) {
|
|
841
|
+
await this.notificationService.createAdhocWorkflowNotifications({
|
|
842
|
+
channels: null,
|
|
843
|
+
instance,
|
|
844
|
+
manager,
|
|
845
|
+
message: `案件「${instance.title}」的臨時加簽已被拒絕${decisionComment ? `:${decisionComment}` : '。'}`,
|
|
846
|
+
payload: {
|
|
847
|
+
adhocTaskId: task.id,
|
|
848
|
+
directiveId: directive.id,
|
|
849
|
+
nodeId: task.nodeId,
|
|
850
|
+
originTaskId: originTask.id,
|
|
851
|
+
type: 'PRE_APPROVAL_RETURNED',
|
|
852
|
+
},
|
|
853
|
+
recipientMemberIds,
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
await activityRepository.save(activityRepository.create({
|
|
857
|
+
actorMemberId: task.assigneeMemberId,
|
|
858
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.ADHOC_PRE_APPROVAL_RETURNED,
|
|
859
|
+
instanceId: instance.id,
|
|
860
|
+
nodeId: task.nodeId,
|
|
861
|
+
payload: {
|
|
862
|
+
comment: decisionComment,
|
|
863
|
+
directiveId: directive.id,
|
|
864
|
+
originTaskId: originTask.id,
|
|
865
|
+
rejectedAdhocTaskId: task.id,
|
|
866
|
+
},
|
|
867
|
+
taskId: task.id,
|
|
868
|
+
}));
|
|
869
|
+
return true;
|
|
870
|
+
}
|
|
871
|
+
async readOpenTaskRecipientMemberIds(manager, task) {
|
|
872
|
+
if (task.assigneeMemberId) {
|
|
873
|
+
return [task.assigneeMemberId];
|
|
874
|
+
}
|
|
875
|
+
const candidates = await manager
|
|
876
|
+
.getRepository(task_candidate_entity_1.TaskCandidateEntity)
|
|
877
|
+
.find({ where: { taskId: task.id } });
|
|
878
|
+
return uniqueTexts(candidates
|
|
879
|
+
.filter((candidate) => candidate.status === workflow_engine_enums_1.TaskCandidateStatusEnum.PENDING ||
|
|
880
|
+
candidate.status === workflow_engine_enums_1.TaskCandidateStatusEnum.CLAIMED)
|
|
881
|
+
.map((candidate) => candidate.memberId));
|
|
882
|
+
}
|
|
883
|
+
async dispatchAdhocStageNotifications(manager, instance, nodeId, outcome) {
|
|
884
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
885
|
+
const directives = await directiveRepository.find({
|
|
886
|
+
order: { createdAt: 'ASC' },
|
|
887
|
+
where: {
|
|
888
|
+
instanceId: instance.id,
|
|
889
|
+
originNodeId: nodeId,
|
|
890
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.PENDING,
|
|
891
|
+
type: adhoc_enums_1.AdhocDirectiveTypeEnum.STAGE_NOTIFY,
|
|
892
|
+
},
|
|
893
|
+
});
|
|
894
|
+
if (directives.length === 0) {
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
const node = readWorkflowNodeOrThrow(instance.workflowSnapshot, nodeId);
|
|
898
|
+
const nodeLabel = node.data.label;
|
|
899
|
+
const outcomeLabel = readAdhocStageOutcomeLabel(outcome);
|
|
900
|
+
const consumedAt = new Date();
|
|
901
|
+
await directives.reduce(async (previous, directive) => {
|
|
902
|
+
await previous;
|
|
903
|
+
await this.dispatchAdhocDirectiveNotification(manager, instance, directive, `案件「${instance.title}」的階段「${nodeLabel}」已${outcomeLabel}。`, {
|
|
904
|
+
nodeId,
|
|
905
|
+
nodeLabel,
|
|
906
|
+
outcome,
|
|
907
|
+
type: adhoc_enums_1.AdhocDirectiveTypeEnum.STAGE_NOTIFY,
|
|
908
|
+
});
|
|
909
|
+
await directiveRepository.save({
|
|
910
|
+
...directive,
|
|
911
|
+
consumedAt,
|
|
912
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.CONSUMED,
|
|
913
|
+
});
|
|
914
|
+
}, Promise.resolve());
|
|
915
|
+
}
|
|
916
|
+
async dispatchAdhocCompletionNotifications(manager, instance, finalState) {
|
|
917
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
918
|
+
const directives = await directiveRepository.find({
|
|
919
|
+
order: { createdAt: 'ASC' },
|
|
920
|
+
where: {
|
|
921
|
+
instanceId: instance.id,
|
|
922
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.PENDING,
|
|
923
|
+
type: adhoc_enums_1.AdhocDirectiveTypeEnum.COMPLETION_NOTIFY,
|
|
924
|
+
},
|
|
925
|
+
});
|
|
926
|
+
if (directives.length === 0) {
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
const finalStateLabel = readInstanceFinalStateLabel(finalState);
|
|
930
|
+
const consumedAt = new Date();
|
|
931
|
+
await directives.reduce(async (previous, directive) => {
|
|
932
|
+
await previous;
|
|
933
|
+
await this.dispatchAdhocDirectiveNotification(manager, instance, directive, `案件「${instance.title}」已結案(${finalStateLabel})。`, {
|
|
934
|
+
finalState,
|
|
935
|
+
type: adhoc_enums_1.AdhocDirectiveTypeEnum.COMPLETION_NOTIFY,
|
|
936
|
+
});
|
|
937
|
+
await directiveRepository.save({
|
|
938
|
+
...directive,
|
|
939
|
+
consumedAt,
|
|
940
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.CONSUMED,
|
|
941
|
+
});
|
|
942
|
+
}, Promise.resolve());
|
|
943
|
+
}
|
|
944
|
+
async dispatchAdhocDirectiveNotification(manager, instance, directive, message, payload) {
|
|
945
|
+
const targetValue = readAdhocTargetValue(directive);
|
|
946
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
947
|
+
if (targetValue.kind === adhoc_enums_1.AdhocTargetKindEnum.WEBHOOK) {
|
|
948
|
+
const result = await executeAdhocWebhookDispatch(this.serviceTaskDispatcher, targetValue, {
|
|
949
|
+
...payload,
|
|
950
|
+
instanceId: instance.id,
|
|
951
|
+
instanceTitle: instance.title,
|
|
952
|
+
message,
|
|
953
|
+
triggeredAt: new Date().toISOString(),
|
|
954
|
+
});
|
|
955
|
+
await activityRepository.save(activityRepository.create({
|
|
956
|
+
actorMemberId: null,
|
|
957
|
+
eventType: result.ok
|
|
958
|
+
? workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_EXECUTED
|
|
959
|
+
: workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
|
|
960
|
+
instanceId: instance.id,
|
|
961
|
+
nodeId: directive.originNodeId,
|
|
962
|
+
payload: {
|
|
963
|
+
action: 'ADHOC_WEBHOOK',
|
|
964
|
+
directiveId: directive.id,
|
|
965
|
+
error: result.error,
|
|
966
|
+
ok: result.ok,
|
|
967
|
+
status: result.status,
|
|
968
|
+
url: targetValue.webhookUrl,
|
|
969
|
+
},
|
|
970
|
+
taskId: null,
|
|
971
|
+
}));
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
try {
|
|
975
|
+
const recipients = await this.resolveApproverResolver(manager, instance, buildAdhocApproverResolver(targetValue), '臨時通知對象');
|
|
976
|
+
await this.notificationService.createAdhocWorkflowNotifications({
|
|
977
|
+
channels: directive.channels,
|
|
978
|
+
instance,
|
|
979
|
+
manager,
|
|
980
|
+
message,
|
|
981
|
+
payload: { ...payload, directiveId: directive.id },
|
|
982
|
+
recipientMemberIds: recipients.map((recipient) => recipient.memberId),
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
catch (error) {
|
|
986
|
+
// Notification target resolution must never block the workflow
|
|
987
|
+
// transition that triggered it — record the failure instead.
|
|
988
|
+
await activityRepository.save(activityRepository.create({
|
|
989
|
+
actorMemberId: null,
|
|
990
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
|
|
991
|
+
instanceId: instance.id,
|
|
992
|
+
nodeId: directive.originNodeId,
|
|
993
|
+
payload: {
|
|
994
|
+
action: 'ADHOC_NOTIFY',
|
|
995
|
+
directiveId: directive.id,
|
|
996
|
+
error: error instanceof Error
|
|
997
|
+
? error.message
|
|
998
|
+
: 'Ad-hoc notification dispatch failed',
|
|
999
|
+
},
|
|
1000
|
+
taskId: null,
|
|
1001
|
+
}));
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
async cancelPendingAdhocDirectives(manager, instanceId, types) {
|
|
1005
|
+
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
1006
|
+
const directives = await directiveRepository.find({
|
|
1007
|
+
where: {
|
|
1008
|
+
instanceId,
|
|
1009
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.PENDING,
|
|
1010
|
+
...(types ? { type: (0, typeorm_2.In)([...types]) } : {}),
|
|
1011
|
+
},
|
|
1012
|
+
});
|
|
1013
|
+
if (directives.length === 0) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
await directiveRepository.save(directives.map((directive) => ({
|
|
1017
|
+
...directive,
|
|
1018
|
+
status: adhoc_enums_1.AdhocDirectiveStatusEnum.CANCELLED,
|
|
1019
|
+
})));
|
|
1020
|
+
}
|
|
1021
|
+
dryRunApprovalWorkflow(input) {
|
|
1022
|
+
const workflowDefinition = parseWorkflowDefinition(input.workflowDefinitionJson);
|
|
1023
|
+
const formData = parseJsonObject(input.formDataJson, 'formDataJson');
|
|
1024
|
+
const initiatorMetadataSnapshot = input.initiatorMetadataSnapshotJson
|
|
1025
|
+
? parseJsonObject(input.initiatorMetadataSnapshotJson, 'initiatorMetadataSnapshotJson')
|
|
1026
|
+
: readDefaultInitiatorMetadataSnapshot(input.initiatorMemberId);
|
|
1027
|
+
try {
|
|
1028
|
+
return Object.assign(new workflow_dry_run_object_1.WorkflowDryRunResultObject(), {
|
|
1029
|
+
errors: [],
|
|
1030
|
+
steps: this.simulateWorkflow({
|
|
1031
|
+
formData,
|
|
1032
|
+
initiatorMemberId: input.initiatorMemberId,
|
|
1033
|
+
initiatorMetadataSnapshot,
|
|
1034
|
+
workflowDefinition,
|
|
1035
|
+
}),
|
|
1036
|
+
valid: true,
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
catch (error) {
|
|
1040
|
+
return Object.assign(new workflow_dry_run_object_1.WorkflowDryRunResultObject(), {
|
|
1041
|
+
errors: [error instanceof Error ? error.message : 'Dry run failed'],
|
|
1042
|
+
steps: [],
|
|
1043
|
+
valid: false,
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
simulateWorkflow(input) {
|
|
1048
|
+
const startNode = input.workflowDefinition.nodes.find((node) => node.type === 'startEvent');
|
|
1049
|
+
if (!startNode) {
|
|
1050
|
+
throw new common_1.BadRequestException('Workflow does not include a startEvent');
|
|
1051
|
+
}
|
|
1052
|
+
return this.simulateWorkflowPath({
|
|
1053
|
+
...input,
|
|
1054
|
+
depth: 0,
|
|
1055
|
+
incomingEdge: null,
|
|
1056
|
+
lastDecision: null,
|
|
1057
|
+
nodeId: startNode.id,
|
|
1058
|
+
steps: [],
|
|
1059
|
+
visitedNodeIds: new Set(),
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
simulateWorkflowPath(input) {
|
|
1063
|
+
if (input.depth > MAX_PROCESSING_STEPS) {
|
|
1064
|
+
throw new common_1.ConflictException('Dry run exceeded maximum processing steps');
|
|
1065
|
+
}
|
|
1066
|
+
const node = readWorkflowNodeOrThrow(input.workflowDefinition, input.nodeId);
|
|
1067
|
+
const visitKey = `${input.nodeId}:${input.incomingEdge?.id ?? 'start'}`;
|
|
1068
|
+
const entryCondition = readNodeEntryCondition(node);
|
|
1069
|
+
if (input.visitedNodeIds.has(visitKey)) {
|
|
1070
|
+
return [
|
|
1071
|
+
...input.steps,
|
|
1072
|
+
createDryRunStep({
|
|
1073
|
+
assigneeMemberId: null,
|
|
1074
|
+
edge: input.incomingEdge,
|
|
1075
|
+
edgeMatched: null,
|
|
1076
|
+
edgeReason: null,
|
|
1077
|
+
entryCondition,
|
|
1078
|
+
entryConditionMet: null,
|
|
1079
|
+
message: '偵測到循環路徑,已停止此分支。',
|
|
1080
|
+
node,
|
|
1081
|
+
status: 'STOPPED',
|
|
1082
|
+
stepIndex: input.steps.length,
|
|
1083
|
+
}),
|
|
1084
|
+
];
|
|
1085
|
+
}
|
|
1086
|
+
const nextVisitedNodeIds = new Set([...input.visitedNodeIds, visitKey]);
|
|
1087
|
+
const entryConditionMet = this.conditionService.evaluateBoolean(entryCondition, buildDryRunExpressionContext(input, input.lastDecision), `workflow.nodes.${node.id}.data.entryCondition`);
|
|
1088
|
+
const assigneeMemberId = entryConditionMet && node.type === 'userTask'
|
|
1089
|
+
? this.resolveDryRunAssigneeMemberId(input, node.data.approverResolver, `workflow.nodes.${node.id}.data.approverResolver`)
|
|
1090
|
+
: null;
|
|
1091
|
+
const nextLastDecision = entryConditionMet && node.type === 'userTask'
|
|
1092
|
+
? {
|
|
1093
|
+
action: workflow_engine_enums_1.TaskDecisionActionEnum.APPROVED,
|
|
1094
|
+
assigneeMemberId,
|
|
1095
|
+
nodeId: node.id,
|
|
1096
|
+
}
|
|
1097
|
+
: input.lastDecision;
|
|
1098
|
+
const nextSteps = [
|
|
1099
|
+
...input.steps,
|
|
1100
|
+
createDryRunStep({
|
|
1101
|
+
assigneeMemberId,
|
|
1102
|
+
edge: input.incomingEdge,
|
|
1103
|
+
edgeMatched: input.incomingEdge ? true : null,
|
|
1104
|
+
edgeReason: readDryRunEdgeReason(input.incomingEdge),
|
|
1105
|
+
entryCondition,
|
|
1106
|
+
entryConditionMet,
|
|
1107
|
+
message: readDryRunStepMessage(node, entryConditionMet),
|
|
1108
|
+
node,
|
|
1109
|
+
status: readDryRunStepStatus(node, entryConditionMet),
|
|
1110
|
+
stepIndex: input.steps.length,
|
|
1111
|
+
}),
|
|
1112
|
+
];
|
|
1113
|
+
if (!entryConditionMet || node.type === 'endEvent') {
|
|
1114
|
+
if (node.type === 'endEvent') {
|
|
1115
|
+
return nextSteps;
|
|
1116
|
+
}
|
|
1117
|
+
return readOutgoingEdgesOrThrow(input.workflowDefinition, node.id).reduce((steps, edge) => this.simulateWorkflowPath({
|
|
1118
|
+
...input,
|
|
1119
|
+
depth: input.depth + 1,
|
|
1120
|
+
incomingEdge: edge,
|
|
1121
|
+
lastDecision: nextLastDecision,
|
|
1122
|
+
nodeId: edge.target,
|
|
1123
|
+
steps,
|
|
1124
|
+
visitedNodeIds: nextVisitedNodeIds,
|
|
1125
|
+
}), nextSteps);
|
|
1126
|
+
}
|
|
1127
|
+
const outgoingEdges = this.readDryRunAvailableOutgoingEdges(input, node, nextLastDecision);
|
|
1128
|
+
return outgoingEdges.reduce((steps, edge) => this.simulateWorkflowPath({
|
|
1129
|
+
...input,
|
|
1130
|
+
depth: input.depth + 1,
|
|
1131
|
+
incomingEdge: edge,
|
|
1132
|
+
lastDecision: nextLastDecision,
|
|
1133
|
+
nodeId: edge.target,
|
|
1134
|
+
steps,
|
|
1135
|
+
visitedNodeIds: nextVisitedNodeIds,
|
|
1136
|
+
}), nextSteps);
|
|
1137
|
+
}
|
|
1138
|
+
readDryRunAvailableOutgoingEdges(input, node, lastDecision) {
|
|
1139
|
+
if (node.type === 'endEvent') {
|
|
1140
|
+
return [];
|
|
1141
|
+
}
|
|
1142
|
+
const outgoingEdges = readOutgoingEdgesOrThrow(input.workflowDefinition, node.id);
|
|
1143
|
+
const conditionalEdges = outgoingEdges.filter((edge) => !edge.data.isDefault && edgeHasCondition(edge));
|
|
1144
|
+
if (conditionalEdges.length === 0) {
|
|
1145
|
+
return outgoingEdges;
|
|
1146
|
+
}
|
|
1147
|
+
const matchingEdges = conditionalEdges.filter((edge) => this.evaluateDryRunEdge(input, edge, lastDecision));
|
|
1148
|
+
if (matchingEdges.length > 0) {
|
|
1149
|
+
return node.type === 'exclusiveGateway'
|
|
1150
|
+
? [matchingEdges[0]]
|
|
1151
|
+
: matchingEdges;
|
|
1152
|
+
}
|
|
1153
|
+
const defaultEdges = outgoingEdges.filter((edge) => edge.data.isDefault);
|
|
1154
|
+
if (defaultEdges.length > 0) {
|
|
1155
|
+
return node.type === 'exclusiveGateway'
|
|
1156
|
+
? [defaultEdges[0]]
|
|
1157
|
+
: defaultEdges;
|
|
1158
|
+
}
|
|
1159
|
+
throw new common_1.ConflictException(`Workflow node ${node.id} has no matching outgoing edge`);
|
|
1160
|
+
}
|
|
1161
|
+
evaluateDryRunEdge(input, edge, lastDecision) {
|
|
1162
|
+
const expression = edge.data.condition?.trim();
|
|
1163
|
+
if (expression) {
|
|
1164
|
+
return this.conditionService.evaluateBoolean(expression, buildDryRunExpressionContext(input, lastDecision), `workflow.edges.${edge.id}.data.condition`);
|
|
1165
|
+
}
|
|
1166
|
+
return (0, workflow_condition_evaluator_1.evaluateWorkflowEdgeCondition)(edge, { formData: input.formData });
|
|
1167
|
+
}
|
|
1168
|
+
resolveDryRunAssigneeMemberId(input, resolver, label) {
|
|
1169
|
+
if (resolver.type === 'DIRECT') {
|
|
1170
|
+
return readMemberIdFromValue(resolver.memberIds, label);
|
|
1171
|
+
}
|
|
1172
|
+
if (resolver.type === 'DYNAMIC_FORM') {
|
|
1173
|
+
return readMemberIdFromValue(readValueAtPath(input.formData, resolver.formPath), `${label}.formPath`);
|
|
1174
|
+
}
|
|
1175
|
+
if (resolver.type === 'EXPRESSION') {
|
|
1176
|
+
return readMemberIdFromValue(this.conditionService.evaluateValue(resolver.expression, buildDryRunExpressionContext(input, null), `${label}.expression`), `${label}.expression`);
|
|
1177
|
+
}
|
|
1178
|
+
if (resolver.type === 'POSITION') {
|
|
1179
|
+
return `position:${resolver.positionId}`;
|
|
1180
|
+
}
|
|
1181
|
+
if (resolver.type === 'ORG_UNIT_MEMBER') {
|
|
1182
|
+
return `orgUnitMember:${resolver.orgUnitId}`;
|
|
1183
|
+
}
|
|
1184
|
+
if (resolver.type === 'ORG_UNIT_POSITION') {
|
|
1185
|
+
return `orgUnitPosition:${resolver.orgUnitId}:${resolver.positionId}`;
|
|
1186
|
+
}
|
|
1187
|
+
if (resolver.type === 'ORG_UNIT_MANAGER') {
|
|
1188
|
+
return `orgUnitManager:${resolver.orgUnitId}`;
|
|
1189
|
+
}
|
|
1190
|
+
return readMemberIdFromValue(readManagerMemberIdFromInitiatorSnapshot(input.initiatorMetadataSnapshot, resolver.levelsUp), label);
|
|
1191
|
+
}
|
|
1192
|
+
async getApprovalInstance(id, scope) {
|
|
1193
|
+
const instance = await this.approvalInstanceRepository.findOne({
|
|
1194
|
+
where: { id },
|
|
1195
|
+
});
|
|
1196
|
+
if (!instance) {
|
|
1197
|
+
throw new common_1.NotFoundException(`Approval instance ${id} was not found`);
|
|
1198
|
+
}
|
|
1199
|
+
if (scope &&
|
|
1200
|
+
!canReadAllWorkflows(scope) &&
|
|
1201
|
+
!(await this.isApprovalInstanceReadableByMember(instance, scope.memberId))) {
|
|
1202
|
+
throw new common_1.NotFoundException(`Approval instance ${id} was not found`);
|
|
1203
|
+
}
|
|
1204
|
+
return instance;
|
|
1205
|
+
}
|
|
1206
|
+
async listApprovalInstances(scope, options = {}) {
|
|
1207
|
+
const queryBuilder = await this.createFilteredApprovalInstanceQueryBuilder(scope, options);
|
|
1208
|
+
if (!queryBuilder) {
|
|
1209
|
+
return [];
|
|
1210
|
+
}
|
|
1211
|
+
queryBuilder.orderBy('"approvalInstance"."created_at"', 'DESC');
|
|
1212
|
+
if (shouldPaginateList(options)) {
|
|
1213
|
+
queryBuilder
|
|
1214
|
+
.skip(readPageOffset(options.page, options.pageSize))
|
|
1215
|
+
.take(normalizePageSize(options.pageSize));
|
|
1216
|
+
}
|
|
1217
|
+
return queryBuilder.getMany();
|
|
1218
|
+
}
|
|
1219
|
+
async countApprovalInstances(scope, options = {}) {
|
|
1220
|
+
const queryBuilder = await this.createFilteredApprovalInstanceQueryBuilder(scope, options);
|
|
1221
|
+
return queryBuilder ? queryBuilder.getCount() : 0;
|
|
1222
|
+
}
|
|
1223
|
+
async readApprovalInstancePageInfo(scope, options = {}) {
|
|
1224
|
+
const totalCount = await this.countApprovalInstances(scope, options);
|
|
1225
|
+
const page = normalizePage(options.page);
|
|
1226
|
+
const pageSize = normalizePageSize(options.pageSize);
|
|
1227
|
+
const totalPages = Math.max(Math.ceil(totalCount / pageSize), 1);
|
|
1228
|
+
return {
|
|
1229
|
+
hasNextPage: page < totalPages,
|
|
1230
|
+
hasPreviousPage: page > 1,
|
|
1231
|
+
page,
|
|
1232
|
+
pageSize,
|
|
1233
|
+
totalCount,
|
|
1234
|
+
totalPages,
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
async createFilteredApprovalInstanceQueryBuilder(scope, options = {}) {
|
|
1238
|
+
const queryBuilder = this.approvalInstanceRepository.createQueryBuilder('approvalInstance');
|
|
1239
|
+
const hasReadableInstances = this.applyReadableInstanceFilter(queryBuilder, scope, options);
|
|
1240
|
+
if (!hasReadableInstances) {
|
|
1241
|
+
return null;
|
|
1242
|
+
}
|
|
1243
|
+
const templateId = normalizeText(options.templateId);
|
|
1244
|
+
if (options.state?.length) {
|
|
1245
|
+
queryBuilder.andWhere('"approvalInstance"."state" IN (:...states)', {
|
|
1246
|
+
states: [...options.state],
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
if (templateId) {
|
|
1250
|
+
queryBuilder.andWhere('"approvalInstance"."template_id" = :templateId', {
|
|
1251
|
+
templateId,
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
applyApprovalInstanceSearchFilter(queryBuilder, options.searchText);
|
|
1255
|
+
return queryBuilder;
|
|
1256
|
+
}
|
|
1257
|
+
applyReadableInstanceFilter(queryBuilder, scope, options) {
|
|
1258
|
+
if (!scope) {
|
|
1259
|
+
return true;
|
|
1260
|
+
}
|
|
1261
|
+
const view = options.view ?? workflow_engine_enums_1.ApprovalInstanceListViewEnum.ALL;
|
|
1262
|
+
if (view === workflow_engine_enums_1.ApprovalInstanceListViewEnum.SENT) {
|
|
1263
|
+
queryBuilder.andWhere('"approvalInstance"."initiator_member_id" = :readableMemberId', {
|
|
1264
|
+
readableMemberId: scope.memberId,
|
|
1265
|
+
});
|
|
1266
|
+
return true;
|
|
1267
|
+
}
|
|
1268
|
+
if (view === workflow_engine_enums_1.ApprovalInstanceListViewEnum.CC) {
|
|
1269
|
+
queryBuilder.andWhere(`
|
|
1270
|
+
EXISTS (
|
|
1271
|
+
SELECT 1
|
|
1272
|
+
FROM "notifications" "readableNotification"
|
|
1273
|
+
WHERE "readableNotification"."instance_id" = "approvalInstance"."id"
|
|
1274
|
+
AND "readableNotification"."recipient_member_id" = :readableMemberId
|
|
1275
|
+
)
|
|
1276
|
+
`, {
|
|
1277
|
+
readableMemberId: scope.memberId,
|
|
1278
|
+
});
|
|
1279
|
+
return true;
|
|
1280
|
+
}
|
|
1281
|
+
if (canReadAllWorkflows(scope)) {
|
|
1282
|
+
return true;
|
|
1283
|
+
}
|
|
1284
|
+
queryBuilder.andWhere(`
|
|
1285
|
+
(
|
|
1286
|
+
"approvalInstance"."initiator_member_id" = :readableMemberId
|
|
1287
|
+
OR EXISTS (
|
|
1288
|
+
SELECT 1
|
|
1289
|
+
FROM "tasks" "readableTask"
|
|
1290
|
+
WHERE "readableTask"."instance_id" = "approvalInstance"."id"
|
|
1291
|
+
AND (
|
|
1292
|
+
"readableTask"."assignee_member_id" = :readableMemberId
|
|
1293
|
+
OR "readableTask"."original_assignee_member_id" = :readableMemberId
|
|
1294
|
+
)
|
|
1295
|
+
)
|
|
1296
|
+
OR EXISTS (
|
|
1297
|
+
SELECT 1
|
|
1298
|
+
FROM "task_candidates" "readableCandidate"
|
|
1299
|
+
INNER JOIN "tasks" "readableCandidateTask"
|
|
1300
|
+
ON "readableCandidateTask"."id" = "readableCandidate"."task_id"
|
|
1301
|
+
WHERE "readableCandidateTask"."instance_id" = "approvalInstance"."id"
|
|
1302
|
+
AND (
|
|
1303
|
+
"readableCandidate"."member_id" = :readableMemberId
|
|
1304
|
+
OR "readableCandidate"."original_member_id" = :readableMemberId
|
|
1305
|
+
)
|
|
1306
|
+
)
|
|
1307
|
+
OR EXISTS (
|
|
1308
|
+
SELECT 1
|
|
1309
|
+
FROM "task_decisions" "readableDecision"
|
|
1310
|
+
INNER JOIN "tasks" "readableDecisionTask"
|
|
1311
|
+
ON "readableDecisionTask"."id" = "readableDecision"."task_id"
|
|
1312
|
+
WHERE "readableDecisionTask"."instance_id" = "approvalInstance"."id"
|
|
1313
|
+
AND "readableDecision"."decided_by_member_id" = :readableMemberId
|
|
1314
|
+
)
|
|
1315
|
+
)
|
|
1316
|
+
`, {
|
|
1317
|
+
readableMemberId: scope.memberId,
|
|
1318
|
+
});
|
|
1319
|
+
return true;
|
|
1320
|
+
}
|
|
1321
|
+
async readWorkflowDashboardSummary(scope, options = {}) {
|
|
1322
|
+
const instances = (await this.listApprovalInstances(scope, {
|
|
1323
|
+
view: workflow_engine_enums_1.ApprovalInstanceListViewEnum.ALL,
|
|
1324
|
+
})).filter((instance) => isInstanceWithinRange(instance, options));
|
|
1325
|
+
const instanceIds = instances.map((instance) => instance.id);
|
|
1326
|
+
const openTasks = instanceIds.length
|
|
1327
|
+
? await this.taskRepository.find({
|
|
1328
|
+
where: {
|
|
1329
|
+
instanceId: (0, typeorm_2.In)(instanceIds),
|
|
1330
|
+
status: (0, typeorm_2.In)([workflow_engine_enums_1.TaskStatusEnum.PENDING, workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS]),
|
|
1331
|
+
},
|
|
1332
|
+
})
|
|
1333
|
+
: [];
|
|
1334
|
+
const now = Date.now();
|
|
1335
|
+
return {
|
|
1336
|
+
activeInstanceCount: instances.filter((instance) => instance.state === workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING).length,
|
|
1337
|
+
completedInstanceCount: instances.filter((instance) => instance.state === workflow_engine_enums_1.ApprovalInstanceStateEnum.APPROVED).length,
|
|
1338
|
+
overdueTaskCount: openTasks.filter((task) => task.slaDueAt && task.slaDueAt.getTime() <= now).length,
|
|
1339
|
+
pendingTaskCount: openTasks.length,
|
|
1340
|
+
rejectedInstanceCount: instances.filter((instance) => instance.state === workflow_engine_enums_1.ApprovalInstanceStateEnum.REJECTED).length,
|
|
1341
|
+
totalInstanceCount: instances.length,
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
async listLaunchableApprovalTemplates(memberId) {
|
|
1345
|
+
const templates = await this.approvalTemplateRepository.find({
|
|
1346
|
+
order: { updatedAt: 'DESC' },
|
|
1347
|
+
});
|
|
1348
|
+
const currentVersionIds = templates
|
|
1349
|
+
.map((template) => template.currentVersionId)
|
|
1350
|
+
.filter((versionId) => Boolean(versionId));
|
|
1351
|
+
if (!currentVersionIds.length) {
|
|
1352
|
+
return [];
|
|
1353
|
+
}
|
|
1354
|
+
const versions = await this.approvalTemplateVersionRepository.find({
|
|
1355
|
+
where: {
|
|
1356
|
+
id: (0, typeorm_2.In)(currentVersionIds),
|
|
1357
|
+
status: template_enums_1.ApprovalTemplateVersionStatusEnum.PUBLISHED,
|
|
1358
|
+
},
|
|
1359
|
+
});
|
|
1360
|
+
const versionById = new Map(versions.map((version) => [
|
|
1361
|
+
version.id,
|
|
1362
|
+
version,
|
|
1363
|
+
]));
|
|
1364
|
+
const initiatorMetadataSnapshot = await this.readDefaultInitiatorMetadataSnapshot(memberId);
|
|
1365
|
+
return templates.filter((template) => {
|
|
1366
|
+
const currentVersion = template.currentVersionId
|
|
1367
|
+
? (versionById.get(template.currentVersionId) ?? null)
|
|
1368
|
+
: null;
|
|
1369
|
+
if (!currentVersion?.formDefinitionVersionId) {
|
|
1370
|
+
return false;
|
|
1371
|
+
}
|
|
1372
|
+
return this.conditionService.evaluateBoolean(currentVersion.initiatorPolicyCel, buildInitiatorPolicyContext(initiatorMetadataSnapshot), 'initiatorPolicyCel');
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
async listWorkflowTokens(instanceId, scope) {
|
|
1376
|
+
await this.getApprovalInstance(instanceId, scope);
|
|
1377
|
+
return this.workflowTokenRepository.find({
|
|
1378
|
+
order: { createdAt: 'ASC' },
|
|
1379
|
+
where: { instanceId },
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
async listTasks(instanceId, scope) {
|
|
1383
|
+
await this.getApprovalInstance(instanceId, scope);
|
|
1384
|
+
return this.attachTaskCandidateSummaries(await this.taskRepository.find({
|
|
1385
|
+
order: { createdAt: 'ASC' },
|
|
1386
|
+
where: { instanceId },
|
|
1387
|
+
}));
|
|
1388
|
+
}
|
|
1389
|
+
async listInboxTasks(assigneeMemberId) {
|
|
1390
|
+
const directTasks = await this.taskRepository.find({
|
|
1391
|
+
order: { createdAt: 'DESC' },
|
|
1392
|
+
where: [
|
|
1393
|
+
{
|
|
1394
|
+
assigneeMemberId,
|
|
1395
|
+
status: (0, typeorm_2.In)([workflow_engine_enums_1.TaskStatusEnum.PENDING, workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS]),
|
|
1396
|
+
},
|
|
1397
|
+
],
|
|
1398
|
+
});
|
|
1399
|
+
const candidateRows = await this.taskCandidateRepository.find({
|
|
1400
|
+
where: {
|
|
1401
|
+
memberId: assigneeMemberId,
|
|
1402
|
+
status: (0, typeorm_2.In)([
|
|
1403
|
+
workflow_engine_enums_1.TaskCandidateStatusEnum.PENDING,
|
|
1404
|
+
workflow_engine_enums_1.TaskCandidateStatusEnum.CLAIMED,
|
|
1405
|
+
]),
|
|
1406
|
+
},
|
|
1407
|
+
});
|
|
1408
|
+
const candidateTasks = candidateRows.length
|
|
1409
|
+
? await this.taskRepository.find({
|
|
1410
|
+
order: { createdAt: 'DESC' },
|
|
1411
|
+
where: {
|
|
1412
|
+
id: (0, typeorm_2.In)(candidateRows.map((candidate) => candidate.taskId)),
|
|
1413
|
+
status: (0, typeorm_2.In)([workflow_engine_enums_1.TaskStatusEnum.PENDING, workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS]),
|
|
1414
|
+
},
|
|
1415
|
+
})
|
|
1416
|
+
: [];
|
|
1417
|
+
return this.attachTaskCandidateSummaries(uniqueTasksById([...directTasks, ...candidateTasks]));
|
|
1418
|
+
}
|
|
1419
|
+
async listApprovalHistoryTasks(assigneeMemberId) {
|
|
1420
|
+
const directTasks = await this.taskRepository.find({
|
|
1421
|
+
order: { completedAt: 'DESC', createdAt: 'DESC' },
|
|
1422
|
+
take: DEFAULT_APPROVAL_HISTORY_TASK_LIMIT,
|
|
1423
|
+
where: { assigneeMemberId, status: workflow_engine_enums_1.TaskStatusEnum.COMPLETED },
|
|
1424
|
+
});
|
|
1425
|
+
const candidateRows = await this.taskCandidateRepository.find({
|
|
1426
|
+
where: {
|
|
1427
|
+
memberId: assigneeMemberId,
|
|
1428
|
+
status: workflow_engine_enums_1.TaskCandidateStatusEnum.COMPLETED,
|
|
1429
|
+
},
|
|
1430
|
+
});
|
|
1431
|
+
const candidateTasks = candidateRows.length
|
|
1432
|
+
? await this.taskRepository.find({
|
|
1433
|
+
order: { completedAt: 'DESC', createdAt: 'DESC' },
|
|
1434
|
+
take: DEFAULT_APPROVAL_HISTORY_TASK_LIMIT,
|
|
1435
|
+
where: {
|
|
1436
|
+
id: (0, typeorm_2.In)(candidateRows.map((candidate) => candidate.taskId)),
|
|
1437
|
+
status: workflow_engine_enums_1.TaskStatusEnum.COMPLETED,
|
|
1438
|
+
},
|
|
1439
|
+
})
|
|
1440
|
+
: [];
|
|
1441
|
+
return this.attachTaskCandidateSummaries(uniqueTasksById([...directTasks, ...candidateTasks]));
|
|
1442
|
+
}
|
|
1443
|
+
async listTaskDecisions(taskId, scope) {
|
|
1444
|
+
await this.assertTaskReadable(taskId, scope);
|
|
1445
|
+
return this.taskDecisionRepository.find({
|
|
1446
|
+
order: { decidedAt: 'ASC' },
|
|
1447
|
+
where: { taskId },
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
async listTaskCandidates(taskId, scope) {
|
|
1451
|
+
await this.assertTaskReadable(taskId, scope);
|
|
1452
|
+
return this.taskCandidateRepository.find({
|
|
1453
|
+
order: { createdAt: 'ASC', id: 'ASC' },
|
|
1454
|
+
where: { taskId },
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
async attachTaskCandidateSummaries(tasks) {
|
|
1458
|
+
if (!tasks.length) {
|
|
1459
|
+
return tasks;
|
|
1460
|
+
}
|
|
1461
|
+
const candidates = await this.taskCandidateRepository.find({
|
|
1462
|
+
order: { createdAt: 'ASC', id: 'ASC' },
|
|
1463
|
+
where: { taskId: (0, typeorm_2.In)(tasks.map((task) => task.id)) },
|
|
1464
|
+
});
|
|
1465
|
+
const candidatesByTaskId = candidates.reduce((currentMap, candidate) => {
|
|
1466
|
+
const nextMap = new Map(currentMap);
|
|
1467
|
+
const memberIds = nextMap.get(candidate.taskId) ?? [];
|
|
1468
|
+
nextMap.set(candidate.taskId, [...memberIds, candidate.memberId]);
|
|
1469
|
+
return nextMap;
|
|
1470
|
+
}, new Map());
|
|
1471
|
+
return tasks.map((task) => Object.assign(new task_entity_1.TaskEntity(), task, {
|
|
1472
|
+
candidateMemberIds: candidatesByTaskId.get(task.id) ?? [],
|
|
1473
|
+
}));
|
|
1474
|
+
}
|
|
1475
|
+
async readDefaultInitiatorMetadataSnapshot(memberId) {
|
|
1476
|
+
const today = toDateOnlyString(new Date());
|
|
1477
|
+
const memberships = (await this.approvalInstanceRepository.manager
|
|
1478
|
+
.getRepository(membership_entity_1.MembershipEntity)
|
|
1479
|
+
.find({ where: { memberId } }))
|
|
1480
|
+
.filter((membership) => isDateRangeActive(membership, today))
|
|
1481
|
+
.sort(compareMembership);
|
|
1482
|
+
const primaryMembership = memberships[0] ?? null;
|
|
1483
|
+
const positionIds = uniqueTexts(memberships
|
|
1484
|
+
.map((membership) => membership.positionId)
|
|
1485
|
+
.filter((positionId) => Boolean(positionId)));
|
|
1486
|
+
return {
|
|
1487
|
+
customFields: {},
|
|
1488
|
+
memberId,
|
|
1489
|
+
orgUnitIds: uniqueTexts(memberships.map((membership) => membership.orgUnitId)),
|
|
1490
|
+
positionId: primaryMembership?.positionId ?? null,
|
|
1491
|
+
positionIds,
|
|
1492
|
+
primaryOrgUnitId: primaryMembership?.orgUnitId ?? null,
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
async listActivityLogs(instanceId, scope) {
|
|
1496
|
+
await this.getApprovalInstance(instanceId, scope);
|
|
1497
|
+
return this.activityLogRepository.find({
|
|
1498
|
+
order: { createdAt: 'ASC' },
|
|
1499
|
+
where: { instanceId },
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
async assertTaskReadable(taskId, scope) {
|
|
1503
|
+
const task = await this.taskRepository.findOne({ where: { id: taskId } });
|
|
1504
|
+
if (!task) {
|
|
1505
|
+
throw new common_1.NotFoundException(`Task ${taskId} was not found`);
|
|
1506
|
+
}
|
|
1507
|
+
await this.getApprovalInstance(task.instanceId, scope);
|
|
1508
|
+
return task;
|
|
1509
|
+
}
|
|
1510
|
+
async isApprovalInstanceReadableByMember(instance, memberId) {
|
|
1511
|
+
if (instance.initiatorMemberId === memberId) {
|
|
1512
|
+
return true;
|
|
1513
|
+
}
|
|
1514
|
+
const tasks = await this.taskRepository.find({
|
|
1515
|
+
where: { instanceId: instance.id },
|
|
1516
|
+
});
|
|
1517
|
+
if (tasks.some((task) => isTaskRelatedToMember(task, memberId))) {
|
|
1518
|
+
return true;
|
|
1519
|
+
}
|
|
1520
|
+
const taskIds = tasks.map((task) => task.id);
|
|
1521
|
+
if (!taskIds.length) {
|
|
1522
|
+
return false;
|
|
1523
|
+
}
|
|
1524
|
+
const candidates = await this.taskCandidateRepository.find({
|
|
1525
|
+
where: { taskId: (0, typeorm_2.In)(taskIds) },
|
|
1526
|
+
});
|
|
1527
|
+
if (candidates.some((candidate) => candidate.memberId === memberId ||
|
|
1528
|
+
candidate.originalMemberId === memberId)) {
|
|
1529
|
+
return true;
|
|
1530
|
+
}
|
|
1531
|
+
const decisions = await this.taskDecisionRepository.find({
|
|
1532
|
+
where: { taskId: (0, typeorm_2.In)(taskIds) },
|
|
1533
|
+
});
|
|
1534
|
+
if (decisions.some((decision) => decision.decidedByMemberId === memberId)) {
|
|
1535
|
+
return true;
|
|
1536
|
+
}
|
|
1537
|
+
// Members who received any notification for this instance (e.g. ad-hoc
|
|
1538
|
+
// stage / completion notify recipients, CC'd service-task recipients) may
|
|
1539
|
+
// open the instance they were notified about.
|
|
1540
|
+
const notification = await this.notificationRepository.findOne({
|
|
1541
|
+
where: { instanceId: instance.id, recipientMemberId: memberId },
|
|
1542
|
+
});
|
|
1543
|
+
return Boolean(notification);
|
|
1544
|
+
}
|
|
1545
|
+
async processActiveToken(manager, instance, token) {
|
|
1546
|
+
const node = readWorkflowNodeOrThrow(instance.workflowSnapshot, token.currentNodeId);
|
|
1547
|
+
if (!(await this.isNodeEntryReady(manager, instance, token, node))) {
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
if (!this.isNodeEntryConditionMet(instance, node)) {
|
|
1551
|
+
await this.skipNodeByEntryCondition(manager, instance, token, node);
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
if (node.type === 'startEvent') {
|
|
1555
|
+
await this.advanceTokenToOutgoingNodes(manager, instance, token, node);
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
if (node.type === 'endEvent') {
|
|
1559
|
+
await this.completeTokenAtEndNode(manager, instance, token, node);
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
if (node.type === 'userTask') {
|
|
1563
|
+
await this.createUserTaskForToken(manager, instance, token, node);
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
if (node.type === 'serviceTask') {
|
|
1567
|
+
await this.executeServiceTask(manager, instance, token, node);
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
if (node.type === 'exclusiveGateway') {
|
|
1571
|
+
await this.processExclusiveGateway(manager, instance, token, node);
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
if (node.type === 'parallelGateway') {
|
|
1575
|
+
await this.advanceTokenToOutgoingNodes(manager, instance, token, node);
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
async processRunningInstance(manager, instance) {
|
|
1581
|
+
for (let step = 0; step < MAX_PROCESSING_STEPS; step += 1) {
|
|
1582
|
+
const token = await manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity).findOne({
|
|
1583
|
+
order: { createdAt: 'ASC' },
|
|
1584
|
+
where: {
|
|
1585
|
+
instanceId: instance.id,
|
|
1586
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.ACTIVE,
|
|
1587
|
+
},
|
|
1588
|
+
});
|
|
1589
|
+
if (!token) {
|
|
1590
|
+
await this.completeInstanceIfNoOpenRuntimeState(manager, instance);
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
await this.processActiveToken(manager, instance, token);
|
|
1594
|
+
}
|
|
1595
|
+
throw new common_1.ConflictException(`Approval instance ${instance.id} exceeded maximum processing steps`);
|
|
1596
|
+
}
|
|
1597
|
+
async completeInstanceIfNoOpenRuntimeState(manager, instance) {
|
|
1598
|
+
if (instance.state !== workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING) {
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
const waitingTokens = await manager
|
|
1602
|
+
.getRepository(workflow_token_entity_1.WorkflowTokenEntity)
|
|
1603
|
+
.find({
|
|
1604
|
+
where: {
|
|
1605
|
+
instanceId: instance.id,
|
|
1606
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.WAITING,
|
|
1607
|
+
},
|
|
1608
|
+
});
|
|
1609
|
+
if (waitingTokens.length > 0) {
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
const completedAt = new Date();
|
|
1613
|
+
const completedInstance = await manager
|
|
1614
|
+
.getRepository(approval_instance_entity_1.ApprovalInstanceEntity)
|
|
1615
|
+
.save({
|
|
1616
|
+
...instance,
|
|
1617
|
+
completedAt,
|
|
1618
|
+
state: workflow_engine_enums_1.ApprovalInstanceStateEnum.APPROVED,
|
|
1619
|
+
});
|
|
1620
|
+
await this.notificationService.createInstanceCompletedNotification({
|
|
1621
|
+
instance: completedInstance,
|
|
1622
|
+
manager,
|
|
1623
|
+
});
|
|
1624
|
+
await this.dispatchAdhocCompletionNotifications(manager, completedInstance, workflow_engine_enums_1.ApprovalInstanceStateEnum.APPROVED);
|
|
1625
|
+
await this.cancelPendingAdhocDirectives(manager, instance.id, null);
|
|
1626
|
+
}
|
|
1627
|
+
async isNodeEntryReady(manager, instance, token, node) {
|
|
1628
|
+
if (node.type === 'startEvent') {
|
|
1629
|
+
return true;
|
|
1630
|
+
}
|
|
1631
|
+
const incomingEdges = readIncomingEdges(instance.workflowSnapshot, node.id);
|
|
1632
|
+
if (incomingEdges.length < 2) {
|
|
1633
|
+
return true;
|
|
1634
|
+
}
|
|
1635
|
+
const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
|
|
1636
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
1637
|
+
const activeOrWaitingTokens = (await tokenRepository.find({
|
|
1638
|
+
where: { instanceId: instance.id },
|
|
1639
|
+
})).filter((candidate) => candidate.currentNodeId === node.id &&
|
|
1640
|
+
(candidate.status === workflow_engine_enums_1.WorkflowTokenStatusEnum.ACTIVE ||
|
|
1641
|
+
candidate.status === workflow_engine_enums_1.WorkflowTokenStatusEnum.WAITING));
|
|
1642
|
+
const arrivedTokens = activeOrWaitingTokens.some((candidate) => candidate.id === token.id)
|
|
1643
|
+
? activeOrWaitingTokens
|
|
1644
|
+
: [...activeOrWaitingTokens, token];
|
|
1645
|
+
if ((node.data.triggerMode ?? 'AND') === 'OR') {
|
|
1646
|
+
await this.cancelAlternativeTokensForOrTrigger(manager, instance, token, node.id);
|
|
1647
|
+
await this.consumeJoinedTokens(manager, arrivedTokens.filter((candidate) => candidate.id !== token.id));
|
|
1648
|
+
return true;
|
|
1649
|
+
}
|
|
1650
|
+
const pendingTokensThatMayReachNode = (await tokenRepository.find({
|
|
1651
|
+
where: { instanceId: instance.id },
|
|
1652
|
+
})).filter((candidate) => candidate.id !== token.id &&
|
|
1653
|
+
!arrivedTokens.some((arrivedToken) => arrivedToken.id === candidate.id) &&
|
|
1654
|
+
(candidate.status === workflow_engine_enums_1.WorkflowTokenStatusEnum.ACTIVE ||
|
|
1655
|
+
candidate.status === workflow_engine_enums_1.WorkflowTokenStatusEnum.WAITING) &&
|
|
1656
|
+
hasPathToNode(instance.workflowSnapshot, candidate.currentNodeId, node.id));
|
|
1657
|
+
if (arrivedTokens.length < incomingEdges.length &&
|
|
1658
|
+
pendingTokensThatMayReachNode.length > 0) {
|
|
1659
|
+
await tokenRepository.save({
|
|
1660
|
+
...token,
|
|
1661
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.WAITING,
|
|
1662
|
+
});
|
|
1663
|
+
await activityRepository.save(activityRepository.create({
|
|
1664
|
+
actorMemberId: null,
|
|
1665
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TOKEN_ADVANCED,
|
|
1666
|
+
instanceId: instance.id,
|
|
1667
|
+
nodeId: node.id,
|
|
1668
|
+
payload: {
|
|
1669
|
+
arrivedCount: arrivedTokens.length,
|
|
1670
|
+
requiredCount: incomingEdges.length,
|
|
1671
|
+
tokenId: token.id,
|
|
1672
|
+
triggerMode: 'AND',
|
|
1673
|
+
},
|
|
1674
|
+
taskId: null,
|
|
1675
|
+
}));
|
|
1676
|
+
return false;
|
|
1677
|
+
}
|
|
1678
|
+
await this.consumeJoinedTokens(manager, arrivedTokens.filter((candidate) => candidate.id !== token.id));
|
|
1679
|
+
return true;
|
|
1680
|
+
}
|
|
1681
|
+
async advanceTokenToOutgoingNodes(manager, instance, token, node) {
|
|
1682
|
+
const edges = await this.readAvailableOutgoingEdges(manager, instance, node);
|
|
1683
|
+
if (edges.length === 1) {
|
|
1684
|
+
await this.advanceTokenAlongEdge(manager, instance, token, node, edges[0]);
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1687
|
+
await this.forkTokenAlongEdges(manager, instance, token, node, edges);
|
|
1688
|
+
}
|
|
1689
|
+
isNodeEntryConditionMet(instance, node) {
|
|
1690
|
+
const entryCondition = readNodeEntryCondition(node);
|
|
1691
|
+
return this.conditionService.evaluateBoolean(entryCondition, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.entryCondition`);
|
|
1692
|
+
}
|
|
1693
|
+
async skipNodeByEntryCondition(manager, instance, token, node) {
|
|
1694
|
+
await manager.getRepository(activity_log_entity_1.ActivityLogEntity).save(manager.getRepository(activity_log_entity_1.ActivityLogEntity).create({
|
|
1695
|
+
actorMemberId: null,
|
|
1696
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TOKEN_ADVANCED,
|
|
1697
|
+
instanceId: instance.id,
|
|
1698
|
+
nodeId: node.id,
|
|
1699
|
+
payload: {
|
|
1700
|
+
skippedByEntryCondition: true,
|
|
1701
|
+
tokenId: token.id,
|
|
1702
|
+
},
|
|
1703
|
+
taskId: null,
|
|
1704
|
+
}));
|
|
1705
|
+
await this.advanceTokenToOutgoingNodes(manager, instance, token, node);
|
|
1706
|
+
}
|
|
1707
|
+
async readAvailableOutgoingEdges(manager, instance, node) {
|
|
1708
|
+
const outgoingEdges = readOutgoingEdgesOrThrow(instance.workflowSnapshot, node.id);
|
|
1709
|
+
const conditionalEdges = outgoingEdges.filter((edge) => !edge.data.isDefault && edgeHasCondition(edge));
|
|
1710
|
+
if (conditionalEdges.length === 0) {
|
|
1711
|
+
return outgoingEdges;
|
|
1712
|
+
}
|
|
1713
|
+
const matchingEdges = (await Promise.all(conditionalEdges.map(async (edge) => (await this.evaluateWorkflowEdge(manager, instance, node, edge))
|
|
1714
|
+
? edge
|
|
1715
|
+
: null))).filter((edge) => edge !== null);
|
|
1716
|
+
if (matchingEdges.length > 0) {
|
|
1717
|
+
return node.type === 'exclusiveGateway'
|
|
1718
|
+
? [matchingEdges[0]]
|
|
1719
|
+
: matchingEdges;
|
|
1720
|
+
}
|
|
1721
|
+
const defaultEdges = outgoingEdges.filter((edge) => edge.data.isDefault);
|
|
1722
|
+
if (defaultEdges.length > 0) {
|
|
1723
|
+
return node.type === 'exclusiveGateway'
|
|
1724
|
+
? [defaultEdges[0]]
|
|
1725
|
+
: defaultEdges;
|
|
1726
|
+
}
|
|
1727
|
+
throw new common_1.ConflictException(`Workflow node ${node.id} has no matching outgoing edge`);
|
|
1728
|
+
}
|
|
1729
|
+
async evaluateWorkflowEdge(manager, instance, node, edge) {
|
|
1730
|
+
const expression = edge.data.condition?.trim();
|
|
1731
|
+
if (expression) {
|
|
1732
|
+
return this.conditionService.evaluateBoolean(expression, {
|
|
1733
|
+
...buildWorkflowExpressionContext(instance),
|
|
1734
|
+
lastDecision: await this.readLastDecisionContext(manager, instance.id, node.id),
|
|
1735
|
+
}, `workflow.edges.${edge.id}.data.condition`);
|
|
1736
|
+
}
|
|
1737
|
+
return (0, workflow_condition_evaluator_1.evaluateWorkflowEdgeCondition)(edge, { formData: instance.formData });
|
|
1738
|
+
}
|
|
1739
|
+
async readLastDecisionContext(manager, instanceId, nodeId) {
|
|
1740
|
+
const tasks = await manager.getRepository(task_entity_1.TaskEntity).find({
|
|
1741
|
+
where: { instanceId, nodeId },
|
|
1742
|
+
});
|
|
1743
|
+
const taskIds = tasks.map((task) => task.id);
|
|
1744
|
+
if (taskIds.length === 0) {
|
|
1745
|
+
return null;
|
|
1746
|
+
}
|
|
1747
|
+
const decision = (await manager.getRepository(task_decision_entity_1.TaskDecisionEntity).find({
|
|
1748
|
+
order: { decidedAt: 'DESC' },
|
|
1749
|
+
where: { taskId: (0, typeorm_2.In)(taskIds) },
|
|
1750
|
+
}))[0];
|
|
1751
|
+
if (!decision) {
|
|
1752
|
+
return null;
|
|
1753
|
+
}
|
|
1754
|
+
return {
|
|
1755
|
+
action: decision.action,
|
|
1756
|
+
comment: decision.comment,
|
|
1757
|
+
decidedAt: decision.decidedAt.toISOString(),
|
|
1758
|
+
decidedByMemberId: decision.decidedByMemberId,
|
|
1759
|
+
returnToNodeId: decision.returnToNodeId,
|
|
1760
|
+
taskId: decision.taskId,
|
|
1761
|
+
transferToMemberId: decision.transferToMemberId,
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
async advanceTokenAlongEdge(manager, instance, token, node, edge) {
|
|
1765
|
+
const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
|
|
1766
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
1767
|
+
const nextToken = await tokenRepository.save({
|
|
1768
|
+
...token,
|
|
1769
|
+
currentNodeId: edge.target,
|
|
1770
|
+
});
|
|
1771
|
+
await activityRepository.save(activityRepository.create({
|
|
1772
|
+
actorMemberId: null,
|
|
1773
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TOKEN_ADVANCED,
|
|
1774
|
+
instanceId: instance.id,
|
|
1775
|
+
nodeId: edge.target,
|
|
1776
|
+
payload: {
|
|
1777
|
+
edgeId: edge.id,
|
|
1778
|
+
fromNodeId: node.id,
|
|
1779
|
+
tokenId: nextToken.id,
|
|
1780
|
+
toNodeId: edge.target,
|
|
1781
|
+
},
|
|
1782
|
+
taskId: null,
|
|
1783
|
+
}));
|
|
1784
|
+
}
|
|
1785
|
+
async forkTokenAlongEdges(manager, instance, token, node, edges) {
|
|
1786
|
+
const forkedAt = new Date();
|
|
1787
|
+
const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
|
|
1788
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
1789
|
+
await tokenRepository.save({
|
|
1790
|
+
...token,
|
|
1791
|
+
consumedAt: forkedAt,
|
|
1792
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.CONSUMED,
|
|
1793
|
+
});
|
|
1794
|
+
const childTokens = await tokenRepository.save(edges.map((edge) => tokenRepository.create({
|
|
1795
|
+
consumedAt: null,
|
|
1796
|
+
currentNodeId: edge.target,
|
|
1797
|
+
instanceId: instance.id,
|
|
1798
|
+
parentTokenId: token.id,
|
|
1799
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.ACTIVE,
|
|
1800
|
+
})));
|
|
1801
|
+
await activityRepository.save([
|
|
1802
|
+
activityRepository.create({
|
|
1803
|
+
actorMemberId: null,
|
|
1804
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TOKEN_ADVANCED,
|
|
1805
|
+
instanceId: instance.id,
|
|
1806
|
+
nodeId: node.id,
|
|
1807
|
+
payload: {
|
|
1808
|
+
edgeIds: edges.map((edge) => edge.id),
|
|
1809
|
+
fromNodeId: node.id,
|
|
1810
|
+
tokenId: token.id,
|
|
1811
|
+
toNodeIds: edges.map((edge) => edge.target),
|
|
1812
|
+
},
|
|
1813
|
+
taskId: null,
|
|
1814
|
+
}),
|
|
1815
|
+
...childTokens.map((childToken, index) => activityRepository.create({
|
|
1816
|
+
actorMemberId: null,
|
|
1817
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TOKEN_CREATED,
|
|
1818
|
+
instanceId: instance.id,
|
|
1819
|
+
nodeId: childToken.currentNodeId,
|
|
1820
|
+
payload: {
|
|
1821
|
+
edgeId: edges[index]?.id,
|
|
1822
|
+
parentTokenId: token.id,
|
|
1823
|
+
tokenId: childToken.id,
|
|
1824
|
+
},
|
|
1825
|
+
taskId: null,
|
|
1826
|
+
})),
|
|
1827
|
+
]);
|
|
1828
|
+
}
|
|
1829
|
+
async completeTokenAtEndNode(manager, instance, token, node) {
|
|
1830
|
+
const completedAt = new Date();
|
|
1831
|
+
const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
|
|
1832
|
+
const instanceRepository = manager.getRepository(approval_instance_entity_1.ApprovalInstanceEntity);
|
|
1833
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
1834
|
+
const endState = node.data.endState ?? 'APPROVED';
|
|
1835
|
+
const instanceState = endState === 'REJECTED'
|
|
1836
|
+
? workflow_engine_enums_1.ApprovalInstanceStateEnum.REJECTED
|
|
1837
|
+
: workflow_engine_enums_1.ApprovalInstanceStateEnum.APPROVED;
|
|
1838
|
+
await tokenRepository.save({
|
|
1839
|
+
...token,
|
|
1840
|
+
consumedAt: completedAt,
|
|
1841
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.CONSUMED,
|
|
1842
|
+
});
|
|
1843
|
+
const hasOpenToken = (await tokenRepository.find({
|
|
1844
|
+
where: { instanceId: instance.id },
|
|
1845
|
+
})).some((candidate) => candidate.id !== token.id &&
|
|
1846
|
+
(candidate.status === workflow_engine_enums_1.WorkflowTokenStatusEnum.ACTIVE ||
|
|
1847
|
+
candidate.status === workflow_engine_enums_1.WorkflowTokenStatusEnum.WAITING));
|
|
1848
|
+
if (!hasOpenToken) {
|
|
1849
|
+
const completedInstance = await instanceRepository.save({
|
|
1850
|
+
...instance,
|
|
1851
|
+
completedAt,
|
|
1852
|
+
state: instanceState,
|
|
1853
|
+
});
|
|
1854
|
+
if (instanceState === workflow_engine_enums_1.ApprovalInstanceStateEnum.APPROVED) {
|
|
1855
|
+
await this.notificationService.createInstanceCompletedNotification({
|
|
1856
|
+
instance: completedInstance,
|
|
1857
|
+
manager,
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
await this.dispatchAdhocCompletionNotifications(manager, completedInstance, instanceState);
|
|
1861
|
+
await this.cancelPendingAdhocDirectives(manager, instance.id, null);
|
|
1862
|
+
}
|
|
1863
|
+
await activityRepository.save(activityRepository.create({
|
|
1864
|
+
actorMemberId: null,
|
|
1865
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TOKEN_ADVANCED,
|
|
1866
|
+
instanceId: instance.id,
|
|
1867
|
+
nodeId: node.id,
|
|
1868
|
+
payload: {
|
|
1869
|
+
endState,
|
|
1870
|
+
instanceState,
|
|
1871
|
+
tokenId: token.id,
|
|
1872
|
+
},
|
|
1873
|
+
taskId: null,
|
|
1874
|
+
}));
|
|
1875
|
+
}
|
|
1876
|
+
async createUserTaskForToken(manager, instance, token, node) {
|
|
1877
|
+
const taskRepository = manager.getRepository(task_entity_1.TaskEntity);
|
|
1878
|
+
const taskCandidateRepository = manager.getRepository(task_candidate_entity_1.TaskCandidateEntity);
|
|
1879
|
+
const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
|
|
1880
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
1881
|
+
const existingTask = await taskRepository.findOne({
|
|
1882
|
+
where: {
|
|
1883
|
+
isAdhoc: false,
|
|
1884
|
+
nodeId: node.id,
|
|
1885
|
+
tokenId: token.id,
|
|
1886
|
+
},
|
|
1887
|
+
});
|
|
1888
|
+
if (existingTask) {
|
|
1889
|
+
await tokenRepository.save({
|
|
1890
|
+
...token,
|
|
1891
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.WAITING,
|
|
1892
|
+
});
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
const candidates = await this.resolveRuntimeTaskCandidates(manager, instance, node);
|
|
1896
|
+
const primaryCandidate = candidates[0];
|
|
1897
|
+
const now = new Date();
|
|
1898
|
+
const task = await taskRepository.save(taskRepository.create({
|
|
1899
|
+
assigneeMemberId: candidates.length === 1 ? primaryCandidate.memberId : null,
|
|
1900
|
+
assignmentType: candidates.length === 1
|
|
1901
|
+
? workflow_engine_enums_1.TaskAssignmentTypeEnum.DIRECT_MEMBER
|
|
1902
|
+
: workflow_engine_enums_1.TaskAssignmentTypeEnum.CANDIDATE_GROUP,
|
|
1903
|
+
completedAt: null,
|
|
1904
|
+
createdAt: now,
|
|
1905
|
+
decisionPolicySnapshot: node.data.decisionPolicy,
|
|
1906
|
+
delegationChain: candidates.length === 1 ? primaryCandidate.delegationChain : [],
|
|
1907
|
+
instanceId: instance.id,
|
|
1908
|
+
nodeId: node.id,
|
|
1909
|
+
openedAt: null,
|
|
1910
|
+
originalAssigneeMemberId: candidates.length === 1 ? primaryCandidate.originalMemberId : null,
|
|
1911
|
+
slaDueAt: (0, notification_service_1.calculateTaskSlaDueAt)({ node, now }),
|
|
1912
|
+
status: workflow_engine_enums_1.TaskStatusEnum.PENDING,
|
|
1913
|
+
tokenId: token.id,
|
|
1914
|
+
}));
|
|
1915
|
+
const savedCandidates = await taskCandidateRepository.save(candidates.map((candidate) => taskCandidateRepository.create({
|
|
1916
|
+
claimedAt: null,
|
|
1917
|
+
createdAt: now,
|
|
1918
|
+
decidedAt: null,
|
|
1919
|
+
delegationChain: candidate.delegationChain,
|
|
1920
|
+
memberId: candidate.memberId,
|
|
1921
|
+
originalMemberId: candidate.originalMemberId,
|
|
1922
|
+
sourceType: candidate.sourceType,
|
|
1923
|
+
status: workflow_engine_enums_1.TaskCandidateStatusEnum.PENDING,
|
|
1924
|
+
taskId: task.id,
|
|
1925
|
+
})));
|
|
1926
|
+
task.candidateMemberIds = savedCandidates.map((candidate) => candidate.memberId);
|
|
1927
|
+
const singleCandidate = savedCandidates.length === 1 ? savedCandidates[0] : null;
|
|
1928
|
+
await tokenRepository.save({
|
|
1929
|
+
...token,
|
|
1930
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.WAITING,
|
|
1931
|
+
});
|
|
1932
|
+
await activityRepository.save(activityRepository.create({
|
|
1933
|
+
actorMemberId: null,
|
|
1934
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TASK_CREATED,
|
|
1935
|
+
instanceId: instance.id,
|
|
1936
|
+
nodeId: node.id,
|
|
1937
|
+
payload: {
|
|
1938
|
+
assignmentType: task.assignmentType,
|
|
1939
|
+
assigneeMemberId: singleCandidate?.memberId ?? null,
|
|
1940
|
+
candidateMemberIds: savedCandidates.map((candidate) => candidate.memberId),
|
|
1941
|
+
originalAssigneeMemberId: singleCandidate?.originalMemberId ?? null,
|
|
1942
|
+
tokenId: token.id,
|
|
1943
|
+
},
|
|
1944
|
+
taskId: task.id,
|
|
1945
|
+
}));
|
|
1946
|
+
await savedCandidates.reduce(async (previous, candidate) => {
|
|
1947
|
+
await previous;
|
|
1948
|
+
await this.notificationService.createTaskAssignedNotification({
|
|
1949
|
+
instance,
|
|
1950
|
+
manager,
|
|
1951
|
+
node,
|
|
1952
|
+
task: Object.assign(new task_entity_1.TaskEntity(), task, {
|
|
1953
|
+
assigneeMemberId: candidate.memberId,
|
|
1954
|
+
delegationChain: candidate.delegationChain,
|
|
1955
|
+
originalAssigneeMemberId: candidate.originalMemberId,
|
|
1956
|
+
}),
|
|
1957
|
+
});
|
|
1958
|
+
}, Promise.resolve());
|
|
1959
|
+
// Pending ad-hoc countersign directives attach to the next user task
|
|
1960
|
+
// created after they were requested — spawn their parallel tasks now.
|
|
1961
|
+
await this.spawnCountersignTasksForNode(manager, instance, token, node, now);
|
|
1962
|
+
}
|
|
1963
|
+
async markTaskCandidateDecision({ action, candidate, candidates, decidedAt, manager, task, }) {
|
|
1964
|
+
const taskCandidateRepository = manager.getRepository(task_candidate_entity_1.TaskCandidateEntity);
|
|
1965
|
+
if (!candidate && candidates.length === 0 && task.assigneeMemberId) {
|
|
1966
|
+
const legacyCandidate = await taskCandidateRepository.save(taskCandidateRepository.create({
|
|
1967
|
+
claimedAt: task.openedAt ?? decidedAt,
|
|
1968
|
+
createdAt: task.createdAt,
|
|
1969
|
+
decidedAt,
|
|
1970
|
+
delegationChain: task.delegationChain,
|
|
1971
|
+
memberId: task.assigneeMemberId,
|
|
1972
|
+
originalMemberId: task.originalAssigneeMemberId ?? task.assigneeMemberId,
|
|
1973
|
+
sourceType: 'DIRECT',
|
|
1974
|
+
status: workflow_engine_enums_1.TaskCandidateStatusEnum.COMPLETED,
|
|
1975
|
+
taskId: task.id,
|
|
1976
|
+
}));
|
|
1977
|
+
return [legacyCandidate];
|
|
1978
|
+
}
|
|
1979
|
+
const completedCandidate = candidate
|
|
1980
|
+
? Object.assign(new task_candidate_entity_1.TaskCandidateEntity(), candidate, {
|
|
1981
|
+
claimedAt: candidate.claimedAt ?? decidedAt,
|
|
1982
|
+
decidedAt,
|
|
1983
|
+
status: workflow_engine_enums_1.TaskCandidateStatusEnum.COMPLETED,
|
|
1984
|
+
})
|
|
1985
|
+
: null;
|
|
1986
|
+
const decisionPolicy = readDecisionPolicySnapshot(task);
|
|
1987
|
+
const shouldCancelOpenCandidates = action !== workflow_engine_enums_1.TaskDecisionActionEnum.APPROVED ||
|
|
1988
|
+
decisionPolicy.type === 'SINGLE' ||
|
|
1989
|
+
decisionPolicy.type === 'PARALLEL_ANY';
|
|
1990
|
+
const nextCandidates = candidates.map((currentCandidate) => {
|
|
1991
|
+
if (currentCandidate.id === completedCandidate?.id) {
|
|
1992
|
+
return completedCandidate;
|
|
1993
|
+
}
|
|
1994
|
+
if (shouldCancelOpenCandidates &&
|
|
1995
|
+
(currentCandidate.status === workflow_engine_enums_1.TaskCandidateStatusEnum.PENDING ||
|
|
1996
|
+
currentCandidate.status === workflow_engine_enums_1.TaskCandidateStatusEnum.CLAIMED)) {
|
|
1997
|
+
return Object.assign(new task_candidate_entity_1.TaskCandidateEntity(), currentCandidate, {
|
|
1998
|
+
decidedAt,
|
|
1999
|
+
status: workflow_engine_enums_1.TaskCandidateStatusEnum.CANCELLED,
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
return currentCandidate;
|
|
2003
|
+
});
|
|
2004
|
+
if (nextCandidates.length > 0) {
|
|
2005
|
+
await taskCandidateRepository.save(nextCandidates);
|
|
2006
|
+
}
|
|
2007
|
+
return nextCandidates;
|
|
2008
|
+
}
|
|
2009
|
+
async transferTask(manager, instance, task, decision, signature, transferToMemberId, decisionComment, decidedAt, actorCandidate) {
|
|
2010
|
+
if (!transferToMemberId) {
|
|
2011
|
+
throw new common_1.BadRequestException('Transfer target member is required');
|
|
2012
|
+
}
|
|
2013
|
+
const taskRepository = manager.getRepository(task_entity_1.TaskEntity);
|
|
2014
|
+
const taskCandidateRepository = manager.getRepository(task_candidate_entity_1.TaskCandidateEntity);
|
|
2015
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
2016
|
+
const transferringMemberId = task.assigneeMemberId;
|
|
2017
|
+
if (!transferringMemberId) {
|
|
2018
|
+
throw new common_1.ConflictException('Task must be claimed before transfer');
|
|
2019
|
+
}
|
|
2020
|
+
const nextDelegationChain = [
|
|
2021
|
+
...readDelegationSteps(task.delegationChain),
|
|
2022
|
+
{
|
|
2023
|
+
from: transferringMemberId,
|
|
2024
|
+
reason: 'MANUAL_TRANSFER',
|
|
2025
|
+
ruleId: null,
|
|
2026
|
+
to: transferToMemberId,
|
|
2027
|
+
},
|
|
2028
|
+
];
|
|
2029
|
+
const transferredTask = await taskRepository.save({
|
|
2030
|
+
...task,
|
|
2031
|
+
completedAt: decidedAt,
|
|
2032
|
+
delegationChain: nextDelegationChain,
|
|
2033
|
+
status: workflow_engine_enums_1.TaskStatusEnum.TRANSFERRED,
|
|
2034
|
+
});
|
|
2035
|
+
const nextTask = await taskRepository.save(taskRepository.create({
|
|
2036
|
+
assigneeMemberId: transferToMemberId,
|
|
2037
|
+
assignmentType: workflow_engine_enums_1.TaskAssignmentTypeEnum.DIRECT_MEMBER,
|
|
2038
|
+
completedAt: null,
|
|
2039
|
+
decisionPolicySnapshot: { type: 'SINGLE' },
|
|
2040
|
+
delegationChain: nextDelegationChain,
|
|
2041
|
+
instanceId: task.instanceId,
|
|
2042
|
+
nodeId: task.nodeId,
|
|
2043
|
+
openedAt: null,
|
|
2044
|
+
originalAssigneeMemberId: task.originalAssigneeMemberId,
|
|
2045
|
+
slaDueAt: task.slaDueAt,
|
|
2046
|
+
status: workflow_engine_enums_1.TaskStatusEnum.PENDING,
|
|
2047
|
+
tokenId: task.tokenId,
|
|
2048
|
+
}));
|
|
2049
|
+
await taskCandidateRepository.save([
|
|
2050
|
+
...(actorCandidate
|
|
2051
|
+
? [
|
|
2052
|
+
taskCandidateRepository.create({
|
|
2053
|
+
...actorCandidate,
|
|
2054
|
+
decidedAt,
|
|
2055
|
+
status: workflow_engine_enums_1.TaskCandidateStatusEnum.TRANSFERRED,
|
|
2056
|
+
}),
|
|
2057
|
+
]
|
|
2058
|
+
: []),
|
|
2059
|
+
taskCandidateRepository.create({
|
|
2060
|
+
claimedAt: null,
|
|
2061
|
+
createdAt: decidedAt,
|
|
2062
|
+
decidedAt: null,
|
|
2063
|
+
delegationChain: nextDelegationChain,
|
|
2064
|
+
memberId: transferToMemberId,
|
|
2065
|
+
originalMemberId: task.originalAssigneeMemberId ?? transferToMemberId,
|
|
2066
|
+
sourceType: 'DIRECT',
|
|
2067
|
+
status: workflow_engine_enums_1.TaskCandidateStatusEnum.PENDING,
|
|
2068
|
+
taskId: nextTask.id,
|
|
2069
|
+
}),
|
|
2070
|
+
]);
|
|
2071
|
+
await activityRepository.save([
|
|
2072
|
+
activityRepository.create({
|
|
2073
|
+
actorMemberId: task.assigneeMemberId,
|
|
2074
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TASK_DECIDED,
|
|
2075
|
+
instanceId: instance.id,
|
|
2076
|
+
nodeId: task.nodeId,
|
|
2077
|
+
payload: {
|
|
2078
|
+
action: workflow_engine_enums_1.TaskDecisionActionEnum.TRANSFERRED,
|
|
2079
|
+
comment: decisionComment,
|
|
2080
|
+
decisionId: decision.id,
|
|
2081
|
+
signatureId: signature.id,
|
|
2082
|
+
signedPayloadHash: signature.signedPayloadHash,
|
|
2083
|
+
transferToMemberId,
|
|
2084
|
+
},
|
|
2085
|
+
taskId: transferredTask.id,
|
|
2086
|
+
}),
|
|
2087
|
+
activityRepository.create({
|
|
2088
|
+
actorMemberId: transferringMemberId,
|
|
2089
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TASK_CREATED,
|
|
2090
|
+
instanceId: instance.id,
|
|
2091
|
+
nodeId: task.nodeId,
|
|
2092
|
+
payload: {
|
|
2093
|
+
assigneeMemberId: transferToMemberId,
|
|
2094
|
+
delegationChain: nextDelegationChain,
|
|
2095
|
+
originalAssigneeMemberId: task.originalAssigneeMemberId,
|
|
2096
|
+
tokenId: task.tokenId,
|
|
2097
|
+
transferredFromTaskId: task.id,
|
|
2098
|
+
},
|
|
2099
|
+
taskId: nextTask.id,
|
|
2100
|
+
}),
|
|
2101
|
+
]);
|
|
2102
|
+
const node = readWorkflowNodeOrThrow(instance.workflowSnapshot, task.nodeId);
|
|
2103
|
+
if (node.type === 'userTask') {
|
|
2104
|
+
await this.notificationService.createTaskAssignedNotification({
|
|
2105
|
+
instance,
|
|
2106
|
+
manager,
|
|
2107
|
+
node,
|
|
2108
|
+
task: nextTask,
|
|
2109
|
+
transferred: true,
|
|
2110
|
+
});
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
async resolveRuntimeTaskCandidates(manager, instance, node) {
|
|
2114
|
+
const resolvedCandidates = await this.resolveApproverResolver(manager, instance, node.data.approverResolver, `簽核節點「${node.data.label}」`);
|
|
2115
|
+
const uniqueCandidates = await this.applyDelegationToResolvedCandidates(instance, node.id, resolvedCandidates);
|
|
2116
|
+
if (uniqueCandidates.length === 0) {
|
|
2117
|
+
throw new common_1.ConflictException(`簽核節點「${node.data.label}」 did not resolve to any member id`);
|
|
2118
|
+
}
|
|
2119
|
+
return uniqueCandidates;
|
|
2120
|
+
}
|
|
2121
|
+
async applyDelegationToResolvedCandidates(instance, nodeId, resolvedCandidates) {
|
|
2122
|
+
const context = {
|
|
2123
|
+
formData: instance.formData,
|
|
2124
|
+
initiatorMemberId: instance.initiatorMemberId,
|
|
2125
|
+
initiatorMetadataSnapshot: instance.initiatorMetadataSnapshot,
|
|
2126
|
+
instanceId: instance.id,
|
|
2127
|
+
nodeId,
|
|
2128
|
+
state: instance.state,
|
|
2129
|
+
templateId: instance.templateId,
|
|
2130
|
+
templateVersionId: instance.templateVersionId,
|
|
2131
|
+
title: instance.title,
|
|
2132
|
+
};
|
|
2133
|
+
const delegatedCandidates = await Promise.all(resolvedCandidates.map(async (candidate) => {
|
|
2134
|
+
const delegationResolution = await this.delegationService.resolveAssignee(candidate.memberId, context);
|
|
2135
|
+
return {
|
|
2136
|
+
delegationChain: delegationResolution.delegationChain,
|
|
2137
|
+
memberId: delegationResolution.finalAssigneeMemberId,
|
|
2138
|
+
originalMemberId: candidate.memberId,
|
|
2139
|
+
sourceType: candidate.sourceType,
|
|
2140
|
+
};
|
|
2141
|
+
}));
|
|
2142
|
+
return uniqueRuntimeCandidates(delegatedCandidates);
|
|
2143
|
+
}
|
|
2144
|
+
async resolveApproverResolver(manager, instance, resolver, label) {
|
|
2145
|
+
if (resolver.type === 'DIRECT') {
|
|
2146
|
+
return readMemberIdsFromValue(resolver.memberIds, label).map((memberId) => ({ memberId, sourceType: resolver.type }));
|
|
2147
|
+
}
|
|
2148
|
+
if (resolver.type === 'DYNAMIC_FORM') {
|
|
2149
|
+
return readMemberIdsFromValue(readValueAtPath(instance.formData, resolver.formPath), `${label}.formPath`).map((memberId) => ({ memberId, sourceType: resolver.type }));
|
|
2150
|
+
}
|
|
2151
|
+
if (resolver.type === 'EXPRESSION') {
|
|
2152
|
+
return readMemberIdsFromValue(this.conditionService.evaluateValue(resolver.expression, buildWorkflowExpressionContext(instance), `${label}.expression`), `${label}.expression`).map((memberId) => ({ memberId, sourceType: resolver.type }));
|
|
2153
|
+
}
|
|
2154
|
+
if (resolver.type === 'POSITION') {
|
|
2155
|
+
return this.resolvePositionCandidates(manager, resolver.positionId, label).then((memberIds) => memberIds.map((memberId) => ({ memberId, sourceType: resolver.type })));
|
|
2156
|
+
}
|
|
2157
|
+
if (resolver.type === 'ORG_UNIT_MEMBER') {
|
|
2158
|
+
return this.resolveOrgUnitMemberCandidates(manager, resolver.orgUnitId, Boolean(resolver.includeDescendants), label).then((memberIds) => memberIds.map((memberId) => ({ memberId, sourceType: resolver.type })));
|
|
2159
|
+
}
|
|
2160
|
+
if (resolver.type === 'ORG_UNIT_POSITION') {
|
|
2161
|
+
return this.resolveOrgUnitPositionCandidates(manager, resolver.orgUnitId, resolver.positionId, Boolean(resolver.includeDescendants), label).then((memberIds) => memberIds.map((memberId) => ({ memberId, sourceType: resolver.type })));
|
|
2162
|
+
}
|
|
2163
|
+
if (resolver.type === 'ORG_UNIT_MANAGER') {
|
|
2164
|
+
return this.resolveOrgUnitManagerCandidates(manager, instance, resolver.orgUnitId, resolver.fallback, label).then((memberIds) => memberIds.map((memberId) => ({ memberId, sourceType: resolver.type })));
|
|
2165
|
+
}
|
|
2166
|
+
return this.resolveInitiatorManagerCandidates(manager, instance, resolver.levelsUp, resolver.fallback, label).then((memberIds) => memberIds.map((memberId) => ({ memberId, sourceType: resolver.type })));
|
|
2167
|
+
}
|
|
2168
|
+
async resolvePositionCandidates(manager, positionId, label) {
|
|
2169
|
+
const today = toDateOnlyString(new Date());
|
|
2170
|
+
const memberships = await manager.getRepository(membership_entity_1.MembershipEntity).find({
|
|
2171
|
+
where: { positionId },
|
|
2172
|
+
});
|
|
2173
|
+
const activeMemberships = memberships
|
|
2174
|
+
.filter((membership) => membership.effectiveFrom <= today &&
|
|
2175
|
+
(!membership.effectiveTo || membership.effectiveTo >= today))
|
|
2176
|
+
.sort((first, second) => {
|
|
2177
|
+
if (first.isPrimary !== second.isPrimary) {
|
|
2178
|
+
return first.isPrimary ? -1 : 1;
|
|
2179
|
+
}
|
|
2180
|
+
return second.effectiveFrom.localeCompare(first.effectiveFrom);
|
|
2181
|
+
});
|
|
2182
|
+
if (!activeMemberships.length) {
|
|
2183
|
+
throw new common_1.ConflictException(`${label} position ${positionId} has no active membership`);
|
|
2184
|
+
}
|
|
2185
|
+
return uniqueTexts(activeMemberships.map((membership) => membership.memberId));
|
|
2186
|
+
}
|
|
2187
|
+
async resolveInitiatorManagerCandidates(manager, instance, levelsUp, fallback, label) {
|
|
2188
|
+
const normalizedLevelsUp = Math.max(Math.trunc(levelsUp), 1);
|
|
2189
|
+
const resolvedManagerMemberIds = await this.resolveManagerMemberIdsFromOrganization(manager, instance.initiatorMemberId, normalizedLevelsUp);
|
|
2190
|
+
if (resolvedManagerMemberIds.length) {
|
|
2191
|
+
return resolvedManagerMemberIds;
|
|
2192
|
+
}
|
|
2193
|
+
return [
|
|
2194
|
+
this.resolveFallbackAssignee(instance, fallback, label, `找不到發起人的第 ${normalizedLevelsUp} 層主管`),
|
|
2195
|
+
];
|
|
2196
|
+
}
|
|
2197
|
+
resolveFallbackAssignee(instance, fallback, label, failureReason) {
|
|
2198
|
+
if (!fallback || fallback.type === 'NONE') {
|
|
2199
|
+
throw new common_1.ConflictException(`${label} 無法建立待簽任務:${failureReason},且未設定改派固定人。請聯絡流程管理員調整主管規則或模板 fallback。`);
|
|
2200
|
+
}
|
|
2201
|
+
if (!fallback.memberId.trim()) {
|
|
2202
|
+
throw new common_1.ConflictException(`${label} 無法建立待簽任務:改派固定人未設定。`);
|
|
2203
|
+
}
|
|
2204
|
+
if (fallback.memberId === instance.initiatorMemberId &&
|
|
2205
|
+
!fallback.allowInitiatorSelfApproval) {
|
|
2206
|
+
throw new common_1.ConflictException(`${label} 無法建立待簽任務:改派固定人是申請人本人,且此節點未允許申請人自簽。`);
|
|
2207
|
+
}
|
|
2208
|
+
return fallback.memberId;
|
|
2209
|
+
}
|
|
2210
|
+
async resolveManagerMemberIdsFromOrganization(manager, memberId, levelsUp) {
|
|
2211
|
+
return this.resolveManagerMemberIdsAtLevel(manager, memberId, toDateOnlyString(new Date()), levelsUp);
|
|
2212
|
+
}
|
|
2213
|
+
async resolveManagerMemberIdsAtLevel(manager, memberId, date, remainingLevels) {
|
|
2214
|
+
const directManagerMemberIds = await this.resolveDirectManagerMemberIds(manager, memberId, date);
|
|
2215
|
+
if (remainingLevels <= 1 || directManagerMemberIds.length === 0) {
|
|
2216
|
+
return directManagerMemberIds;
|
|
2217
|
+
}
|
|
2218
|
+
const nextLevelGroups = await Promise.all(directManagerMemberIds.map((managerMemberId) => this.resolveManagerMemberIdsAtLevel(manager, managerMemberId, date, remainingLevels - 1)));
|
|
2219
|
+
return uniqueTexts(nextLevelGroups.flat());
|
|
2220
|
+
}
|
|
2221
|
+
async resolveDirectManagerMemberIds(manager, memberId, date) {
|
|
2222
|
+
const memberships = await manager.getRepository(membership_entity_1.MembershipEntity).find({
|
|
2223
|
+
where: { memberId },
|
|
2224
|
+
});
|
|
2225
|
+
const activeMemberships = memberships.filter((membership) => isDateRangeActive(membership, date));
|
|
2226
|
+
const directOrgUnitIds = activeMemberships.map((membership) => membership.orgUnitId);
|
|
2227
|
+
const positionIds = activeMemberships
|
|
2228
|
+
.map((membership) => membership.positionId)
|
|
2229
|
+
.filter((positionId) => Boolean(positionId));
|
|
2230
|
+
const orgUnitIds = await this.readOrgUnitAndAncestorIds(manager, directOrgUnitIds);
|
|
2231
|
+
const candidatePairs = [
|
|
2232
|
+
{ scopeId: memberId, scopeType: organization_enums_1.ManagerResolutionScopeTypeEnum.MEMBER },
|
|
2233
|
+
...orgUnitIds.map((scopeId) => ({
|
|
2234
|
+
scopeId,
|
|
2235
|
+
scopeType: organization_enums_1.ManagerResolutionScopeTypeEnum.ORG_UNIT,
|
|
2236
|
+
})),
|
|
2237
|
+
...positionIds.map((scopeId) => ({
|
|
2238
|
+
scopeId,
|
|
2239
|
+
scopeType: organization_enums_1.ManagerResolutionScopeTypeEnum.POSITION,
|
|
2240
|
+
})),
|
|
2241
|
+
];
|
|
2242
|
+
return this.resolveManagerResolutionCandidates(manager, candidatePairs, date);
|
|
2243
|
+
}
|
|
2244
|
+
async resolveOrgUnitManagerCandidates(manager, instance, orgUnitId, fallback, label) {
|
|
2245
|
+
const date = toDateOnlyString(new Date());
|
|
2246
|
+
const orgUnitIds = await this.readOrgUnitAndAncestorIds(manager, [
|
|
2247
|
+
orgUnitId,
|
|
2248
|
+
]);
|
|
2249
|
+
const managerMemberIds = await this.resolveManagerResolutionCandidates(manager, orgUnitIds.map((scopeId) => ({
|
|
2250
|
+
scopeId,
|
|
2251
|
+
scopeType: organization_enums_1.ManagerResolutionScopeTypeEnum.ORG_UNIT,
|
|
2252
|
+
})), date);
|
|
2253
|
+
if (!managerMemberIds.length) {
|
|
2254
|
+
return [
|
|
2255
|
+
this.resolveFallbackAssignee(instance, fallback, label, `找不到指定組織的主管規則`),
|
|
2256
|
+
];
|
|
2257
|
+
}
|
|
2258
|
+
return managerMemberIds;
|
|
2259
|
+
}
|
|
2260
|
+
async readOrgUnitAndAncestorIds(manager, orgUnitIds) {
|
|
2261
|
+
if (!orgUnitIds.length) {
|
|
2262
|
+
return [];
|
|
2263
|
+
}
|
|
2264
|
+
const orgUnitRepository = manager.getRepository(org_unit_entity_1.OrgUnitEntity);
|
|
2265
|
+
const directOrgUnits = await orgUnitRepository.find({
|
|
2266
|
+
where: { deletedAt: (0, typeorm_2.IsNull)(), id: (0, typeorm_2.In)([...orgUnitIds]) },
|
|
2267
|
+
});
|
|
2268
|
+
const ancestorGroups = await Promise.all(directOrgUnits.map((orgUnit) => orgUnitRepository
|
|
2269
|
+
.createQueryBuilder('orgUnit')
|
|
2270
|
+
.where('orgUnit.deleted_at IS NULL')
|
|
2271
|
+
.andWhere('orgUnit.path @> :path', { path: orgUnit.path })
|
|
2272
|
+
.getMany()));
|
|
2273
|
+
const byId = new Map([...directOrgUnits, ...ancestorGroups.flat()].map((orgUnit) => [orgUnit.id, orgUnit]));
|
|
2274
|
+
return [...byId.keys()];
|
|
2275
|
+
}
|
|
2276
|
+
async resolveManagerResolutionCandidates(manager, candidatePairs, date) {
|
|
2277
|
+
const scopeIds = candidatePairs.map((pair) => pair.scopeId);
|
|
2278
|
+
if (!scopeIds.length) {
|
|
2279
|
+
return [];
|
|
2280
|
+
}
|
|
2281
|
+
const resolutions = await manager
|
|
2282
|
+
.getRepository(manager_resolution_entity_1.ManagerResolutionEntity)
|
|
2283
|
+
.find({
|
|
2284
|
+
where: {
|
|
2285
|
+
scopeId: (0, typeorm_2.In)([...scopeIds]),
|
|
2286
|
+
scopeType: (0, typeorm_2.In)([
|
|
2287
|
+
organization_enums_1.ManagerResolutionScopeTypeEnum.MEMBER,
|
|
2288
|
+
organization_enums_1.ManagerResolutionScopeTypeEnum.ORG_UNIT,
|
|
2289
|
+
organization_enums_1.ManagerResolutionScopeTypeEnum.POSITION,
|
|
2290
|
+
]),
|
|
2291
|
+
},
|
|
2292
|
+
});
|
|
2293
|
+
const active = resolutions
|
|
2294
|
+
.filter((resolution) => candidatePairs.some((pair) => pair.scopeId === resolution.scopeId &&
|
|
2295
|
+
pair.scopeType === resolution.scopeType))
|
|
2296
|
+
.filter((resolution) => isDateRangeActive(resolution, date))
|
|
2297
|
+
.sort(compareManagerResolution);
|
|
2298
|
+
return uniqueTexts(active.map((resolution) => resolution.managerMemberId));
|
|
2299
|
+
}
|
|
2300
|
+
async resolveOrgUnitMemberCandidates(manager, orgUnitId, includeDescendants, label) {
|
|
2301
|
+
const orgUnitIds = await this.readOrgUnitScopeIds(manager, orgUnitId, includeDescendants);
|
|
2302
|
+
const today = toDateOnlyString(new Date());
|
|
2303
|
+
const memberships = await manager.getRepository(membership_entity_1.MembershipEntity).find({
|
|
2304
|
+
where: { orgUnitId: (0, typeorm_2.In)([...orgUnitIds]) },
|
|
2305
|
+
});
|
|
2306
|
+
const memberIds = memberships
|
|
2307
|
+
.filter((membership) => isDateRangeActive(membership, today))
|
|
2308
|
+
.sort(compareMembership)
|
|
2309
|
+
.map((membership) => membership.memberId);
|
|
2310
|
+
if (memberIds.length === 0) {
|
|
2311
|
+
throw new common_1.ConflictException(`${label} orgUnit ${orgUnitId} has no active member`);
|
|
2312
|
+
}
|
|
2313
|
+
return uniqueTexts(memberIds);
|
|
2314
|
+
}
|
|
2315
|
+
async resolveOrgUnitPositionCandidates(manager, orgUnitId, positionId, includeDescendants, label) {
|
|
2316
|
+
const orgUnitIds = await this.readOrgUnitScopeIds(manager, orgUnitId, includeDescendants);
|
|
2317
|
+
const today = toDateOnlyString(new Date());
|
|
2318
|
+
const memberships = await manager.getRepository(membership_entity_1.MembershipEntity).find({
|
|
2319
|
+
where: { orgUnitId: (0, typeorm_2.In)([...orgUnitIds]), positionId },
|
|
2320
|
+
});
|
|
2321
|
+
const memberIds = memberships
|
|
2322
|
+
.filter((membership) => isDateRangeActive(membership, today))
|
|
2323
|
+
.sort(compareMembership)
|
|
2324
|
+
.map((membership) => membership.memberId);
|
|
2325
|
+
if (memberIds.length === 0) {
|
|
2326
|
+
throw new common_1.ConflictException(`${label} orgUnit ${orgUnitId} and position ${positionId} has no active membership`);
|
|
2327
|
+
}
|
|
2328
|
+
return uniqueTexts(memberIds);
|
|
2329
|
+
}
|
|
2330
|
+
async readOrgUnitScopeIds(manager, orgUnitId, includeDescendants) {
|
|
2331
|
+
const orgUnitRepository = manager.getRepository(org_unit_entity_1.OrgUnitEntity);
|
|
2332
|
+
const orgUnit = await orgUnitRepository.findOne({
|
|
2333
|
+
where: { deletedAt: (0, typeorm_2.IsNull)(), id: orgUnitId },
|
|
2334
|
+
});
|
|
2335
|
+
if (!orgUnit) {
|
|
2336
|
+
throw new common_1.ConflictException(`Org unit ${orgUnitId} was not found`);
|
|
2337
|
+
}
|
|
2338
|
+
if (!includeDescendants) {
|
|
2339
|
+
return [orgUnit.id];
|
|
2340
|
+
}
|
|
2341
|
+
const descendants = await orgUnitRepository
|
|
2342
|
+
.createQueryBuilder('orgUnit')
|
|
2343
|
+
.where('orgUnit.deleted_at IS NULL')
|
|
2344
|
+
.andWhere('orgUnit.path <@ :path', { path: orgUnit.path })
|
|
2345
|
+
.getMany();
|
|
2346
|
+
return uniqueTexts([orgUnit.id, ...descendants.map((unit) => unit.id)]);
|
|
2347
|
+
}
|
|
2348
|
+
async processExclusiveGateway(manager, instance, token, node) {
|
|
2349
|
+
if (node.data.direction !== 'split') {
|
|
2350
|
+
await this.advanceTokenToOutgoingNodes(manager, instance, token, node);
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
const edge = (await this.readAvailableOutgoingEdges(manager, instance, node))[0];
|
|
2354
|
+
await this.advanceTokenAlongEdge(manager, instance, token, node, edge);
|
|
2355
|
+
}
|
|
2356
|
+
async executeServiceTask(manager, instance, token, node) {
|
|
2357
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
2358
|
+
const action = node.data.action;
|
|
2359
|
+
if (action.type === 'WEBHOOK') {
|
|
2360
|
+
const payload = typeof action.payload === 'string' && action.payload.trim()
|
|
2361
|
+
? this.conditionService.evaluateValue(action.payload, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.action.payload`)
|
|
2362
|
+
: {
|
|
2363
|
+
instance: buildWorkflowExpressionContext(instance).instance,
|
|
2364
|
+
triggeredAt: new Date().toISOString(),
|
|
2365
|
+
};
|
|
2366
|
+
const result = await executeWebhookServiceAction(this.serviceTaskDispatcher, action, payload);
|
|
2367
|
+
await activityRepository.save(activityRepository.create({
|
|
2368
|
+
actorMemberId: null,
|
|
2369
|
+
eventType: result.ok
|
|
2370
|
+
? workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_EXECUTED
|
|
2371
|
+
: workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
|
|
2372
|
+
instanceId: instance.id,
|
|
2373
|
+
nodeId: node.id,
|
|
2374
|
+
payload: {
|
|
2375
|
+
action: 'WEBHOOK',
|
|
2376
|
+
error: result.error,
|
|
2377
|
+
ok: result.ok,
|
|
2378
|
+
status: result.status,
|
|
2379
|
+
tokenId: token.id,
|
|
2380
|
+
url: action.url,
|
|
2381
|
+
},
|
|
2382
|
+
taskId: null,
|
|
2383
|
+
}));
|
|
2384
|
+
await this.advanceTokenToOutgoingNodes(manager, instance, token, node);
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
if (action.type === 'SET_FORM_FIELD') {
|
|
2388
|
+
const value = this.conditionService.evaluateValue(action.value, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.action.value`);
|
|
2389
|
+
const updatedFormData = writeValueAtPath(instance.formData, action.fieldPath, value);
|
|
2390
|
+
const updatedInstance = await manager
|
|
2391
|
+
.getRepository(approval_instance_entity_1.ApprovalInstanceEntity)
|
|
2392
|
+
.save({
|
|
2393
|
+
...instance,
|
|
2394
|
+
formData: updatedFormData,
|
|
2395
|
+
});
|
|
2396
|
+
Object.assign(instance, updatedInstance);
|
|
2397
|
+
await activityRepository.save(activityRepository.create({
|
|
2398
|
+
actorMemberId: null,
|
|
2399
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_EXECUTED,
|
|
2400
|
+
instanceId: instance.id,
|
|
2401
|
+
nodeId: node.id,
|
|
2402
|
+
payload: {
|
|
2403
|
+
action: 'SET_FORM_FIELD',
|
|
2404
|
+
fieldPath: action.fieldPath,
|
|
2405
|
+
tokenId: token.id,
|
|
2406
|
+
},
|
|
2407
|
+
taskId: null,
|
|
2408
|
+
}));
|
|
2409
|
+
await this.advanceTokenToOutgoingNodes(manager, updatedInstance, token, node);
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2412
|
+
const recipients = await this.resolveApproverResolver(manager, instance, action.recipients, `知會節點「${node.data.label}」`);
|
|
2413
|
+
const recipientMemberIds = uniqueTexts(recipients.map((recipient) => recipient.memberId));
|
|
2414
|
+
await this.notificationService.createServiceTaskNotifications({
|
|
2415
|
+
instance,
|
|
2416
|
+
manager,
|
|
2417
|
+
node,
|
|
2418
|
+
recipientMemberIds,
|
|
2419
|
+
});
|
|
2420
|
+
await manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity).save({
|
|
2421
|
+
...token,
|
|
2422
|
+
consumedAt: new Date(),
|
|
2423
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.CONSUMED,
|
|
2424
|
+
});
|
|
2425
|
+
await activityRepository.save(activityRepository.create({
|
|
2426
|
+
actorMemberId: null,
|
|
2427
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TOKEN_ADVANCED,
|
|
2428
|
+
instanceId: instance.id,
|
|
2429
|
+
nodeId: node.id,
|
|
2430
|
+
payload: {
|
|
2431
|
+
action: 'NOTIFY',
|
|
2432
|
+
recipientMemberIds,
|
|
2433
|
+
tokenId: token.id,
|
|
2434
|
+
},
|
|
2435
|
+
taskId: null,
|
|
2436
|
+
}));
|
|
2437
|
+
}
|
|
2438
|
+
async rejectInstance(manager, instance, task, rejectedAt) {
|
|
2439
|
+
const instanceRepository = manager.getRepository(approval_instance_entity_1.ApprovalInstanceEntity);
|
|
2440
|
+
// Dispatch pending ad-hoc notifications for the rejecting node before the
|
|
2441
|
+
// remaining directives are cancelled by the terminal cleanup below.
|
|
2442
|
+
await this.dispatchAdhocStageNotifications(manager, instance, task.nodeId, 'REJECTED');
|
|
2443
|
+
// Consume open tokens and cancel any still-open tasks (parallel branches,
|
|
2444
|
+
// ad-hoc countersign / pre-approval tasks) so nothing actionable lingers
|
|
2445
|
+
// on a rejected instance.
|
|
2446
|
+
await this.consumeOpenRuntimeState(manager, instance, rejectedAt, workflow_engine_enums_1.TaskStatusEnum.CANCELLED);
|
|
2447
|
+
await this.notificationService.supersedeInstanceTaskNotifications({
|
|
2448
|
+
instanceId: instance.id,
|
|
2449
|
+
manager,
|
|
2450
|
+
});
|
|
2451
|
+
const rejectedInstance = await instanceRepository.save({
|
|
2452
|
+
...instance,
|
|
2453
|
+
completedAt: rejectedAt,
|
|
2454
|
+
state: workflow_engine_enums_1.ApprovalInstanceStateEnum.REJECTED,
|
|
2455
|
+
});
|
|
2456
|
+
await this.dispatchAdhocCompletionNotifications(manager, rejectedInstance, workflow_engine_enums_1.ApprovalInstanceStateEnum.REJECTED);
|
|
2457
|
+
await this.cancelPendingAdhocDirectives(manager, instance.id, null);
|
|
2458
|
+
await manager.getRepository(activity_log_entity_1.ActivityLogEntity).save(manager.getRepository(activity_log_entity_1.ActivityLogEntity).create({
|
|
2459
|
+
actorMemberId: task.assigneeMemberId,
|
|
2460
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TOKEN_ADVANCED,
|
|
2461
|
+
instanceId: instance.id,
|
|
2462
|
+
nodeId: task.nodeId,
|
|
2463
|
+
payload: {
|
|
2464
|
+
action: 'REJECTED',
|
|
2465
|
+
taskId: task.id,
|
|
2466
|
+
},
|
|
2467
|
+
taskId: task.id,
|
|
2468
|
+
}));
|
|
2469
|
+
}
|
|
2470
|
+
async returnInstanceToNode(manager, instance, task, returnToNodeId, returnedAt) {
|
|
2471
|
+
if (!returnToNodeId) {
|
|
2472
|
+
throw new common_1.ConflictException('Return target node is required');
|
|
2473
|
+
}
|
|
2474
|
+
const targetNode = readWorkflowNodeOrThrow(instance.workflowSnapshot, returnToNodeId);
|
|
2475
|
+
const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
|
|
2476
|
+
const instanceRepository = manager.getRepository(approval_instance_entity_1.ApprovalInstanceEntity);
|
|
2477
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
2478
|
+
// The returning node's stage has ended (outcome: RETURNED) — dispatch its
|
|
2479
|
+
// pending ad-hoc stage notifications, then drop flow-affecting ad-hoc
|
|
2480
|
+
// directives so they cannot replay after the return/resubmit cycle.
|
|
2481
|
+
await this.dispatchAdhocStageNotifications(manager, instance, task.nodeId, 'RETURNED');
|
|
2482
|
+
await this.cancelPendingAdhocDirectives(manager, instance.id, [
|
|
2483
|
+
adhoc_enums_1.AdhocDirectiveTypeEnum.COUNTERSIGN,
|
|
2484
|
+
adhoc_enums_1.AdhocDirectiveTypeEnum.PRE_APPROVAL,
|
|
2485
|
+
]);
|
|
2486
|
+
await this.consumeOpenRuntimeState(manager, instance, returnedAt, workflow_engine_enums_1.TaskStatusEnum.CANCELLED);
|
|
2487
|
+
const returnedInstance = await instanceRepository.save({
|
|
2488
|
+
...instance,
|
|
2489
|
+
completedAt: null,
|
|
2490
|
+
state: targetNode.type === 'startEvent'
|
|
2491
|
+
? workflow_engine_enums_1.ApprovalInstanceStateEnum.RETURNED
|
|
2492
|
+
: workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING,
|
|
2493
|
+
});
|
|
2494
|
+
const resubmitStrategy = readReturnResubmitStrategy(task, instance);
|
|
2495
|
+
const returnToken = await tokenRepository.save(tokenRepository.create({
|
|
2496
|
+
consumedAt: null,
|
|
2497
|
+
currentNodeId: targetNode.id,
|
|
2498
|
+
instanceId: instance.id,
|
|
2499
|
+
parentTokenId: null,
|
|
2500
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.ACTIVE,
|
|
2501
|
+
}));
|
|
2502
|
+
await activityRepository.save([
|
|
2503
|
+
activityRepository.create({
|
|
2504
|
+
actorMemberId: task.assigneeMemberId,
|
|
2505
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.INSTANCE_RETURNED,
|
|
2506
|
+
instanceId: instance.id,
|
|
2507
|
+
nodeId: task.nodeId,
|
|
2508
|
+
payload: {
|
|
2509
|
+
resubmitStrategy,
|
|
2510
|
+
returnedFromNodeId: task.nodeId,
|
|
2511
|
+
returnToNodeId: targetNode.id,
|
|
2512
|
+
taskId: task.id,
|
|
2513
|
+
},
|
|
2514
|
+
taskId: task.id,
|
|
2515
|
+
}),
|
|
2516
|
+
activityRepository.create({
|
|
2517
|
+
actorMemberId: null,
|
|
2518
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TOKEN_CREATED,
|
|
2519
|
+
instanceId: instance.id,
|
|
2520
|
+
nodeId: targetNode.id,
|
|
2521
|
+
payload: {
|
|
2522
|
+
returnedFromNodeId: task.nodeId,
|
|
2523
|
+
tokenId: returnToken.id,
|
|
2524
|
+
},
|
|
2525
|
+
taskId: null,
|
|
2526
|
+
}),
|
|
2527
|
+
]);
|
|
2528
|
+
if (returnedInstance.state === workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING) {
|
|
2529
|
+
await this.processRunningInstance(manager, returnedInstance);
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
async readLatestReturnedInstanceContext(manager, instanceId) {
|
|
2533
|
+
const activity = await manager.getRepository(activity_log_entity_1.ActivityLogEntity).findOne({
|
|
2534
|
+
order: { createdAt: 'DESC' },
|
|
2535
|
+
where: {
|
|
2536
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.INSTANCE_RETURNED,
|
|
2537
|
+
instanceId,
|
|
2538
|
+
},
|
|
2539
|
+
});
|
|
2540
|
+
if (!activity) {
|
|
2541
|
+
return null;
|
|
2542
|
+
}
|
|
2543
|
+
const returnToNodeId = readStringPayloadValue(activity.payload, 'returnToNodeId');
|
|
2544
|
+
const returnedFromNodeId = readStringPayloadValue(activity.payload, 'returnedFromNodeId') ??
|
|
2545
|
+
activity.nodeId;
|
|
2546
|
+
if (!returnToNodeId || !returnedFromNodeId) {
|
|
2547
|
+
return null;
|
|
2548
|
+
}
|
|
2549
|
+
return {
|
|
2550
|
+
resubmitStrategy: readReturnResubmitStrategyFromPayload(activity.payload),
|
|
2551
|
+
returnedFromNodeId,
|
|
2552
|
+
returnToNodeId,
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
async resumeReturnedInstanceFromReturnPoint(manager, instance, context, resubmittedAt) {
|
|
2556
|
+
const targetNode = readWorkflowNodeOrThrow(instance.workflowSnapshot, context.returnToNodeId);
|
|
2557
|
+
if (targetNode.type !== 'startEvent') {
|
|
2558
|
+
return instance;
|
|
2559
|
+
}
|
|
2560
|
+
readWorkflowNodeOrThrow(instance.workflowSnapshot, context.returnedFromNodeId);
|
|
2561
|
+
await this.consumeOpenRuntimeState(manager, instance, resubmittedAt, workflow_engine_enums_1.TaskStatusEnum.CANCELLED);
|
|
2562
|
+
const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
|
|
2563
|
+
const returnPointToken = await tokenRepository.save(tokenRepository.create({
|
|
2564
|
+
consumedAt: null,
|
|
2565
|
+
currentNodeId: context.returnedFromNodeId,
|
|
2566
|
+
instanceId: instance.id,
|
|
2567
|
+
parentTokenId: null,
|
|
2568
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.ACTIVE,
|
|
2569
|
+
}));
|
|
2570
|
+
await manager.getRepository(activity_log_entity_1.ActivityLogEntity).save(manager.getRepository(activity_log_entity_1.ActivityLogEntity).create({
|
|
2571
|
+
actorMemberId: null,
|
|
2572
|
+
eventType: workflow_engine_enums_1.ActivityLogEventTypeEnum.TOKEN_CREATED,
|
|
2573
|
+
instanceId: instance.id,
|
|
2574
|
+
nodeId: context.returnedFromNodeId,
|
|
2575
|
+
payload: {
|
|
2576
|
+
resubmitStrategy: context.resubmitStrategy,
|
|
2577
|
+
resumedFromNodeId: context.returnToNodeId,
|
|
2578
|
+
tokenId: returnPointToken.id,
|
|
2579
|
+
},
|
|
2580
|
+
taskId: null,
|
|
2581
|
+
}));
|
|
2582
|
+
return instance;
|
|
2583
|
+
}
|
|
2584
|
+
async consumeOpenRuntimeState(manager, instance, consumedAt, taskStatus) {
|
|
2585
|
+
const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
|
|
2586
|
+
const taskRepository = manager.getRepository(task_entity_1.TaskEntity);
|
|
2587
|
+
const tokens = await tokenRepository.find({
|
|
2588
|
+
where: { instanceId: instance.id },
|
|
2589
|
+
});
|
|
2590
|
+
const openTokens = tokens.filter((token) => token.status === workflow_engine_enums_1.WorkflowTokenStatusEnum.ACTIVE ||
|
|
2591
|
+
token.status === workflow_engine_enums_1.WorkflowTokenStatusEnum.WAITING);
|
|
2592
|
+
const tasks = await taskRepository.find({
|
|
2593
|
+
where: { instanceId: instance.id },
|
|
2594
|
+
});
|
|
2595
|
+
const openTasks = tasks.filter((task) => task.status === workflow_engine_enums_1.TaskStatusEnum.PENDING ||
|
|
2596
|
+
task.status === workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS);
|
|
2597
|
+
if (openTokens.length > 0) {
|
|
2598
|
+
await tokenRepository.save(openTokens.map((token) => ({
|
|
2599
|
+
...token,
|
|
2600
|
+
consumedAt,
|
|
2601
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.CONSUMED,
|
|
2602
|
+
})));
|
|
2603
|
+
}
|
|
2604
|
+
if (openTasks.length > 0) {
|
|
2605
|
+
await taskRepository.save(openTasks.map((openTask) => Object.assign(new task_entity_1.TaskEntity(), openTask, {
|
|
2606
|
+
completedAt: consumedAt,
|
|
2607
|
+
status: taskStatus,
|
|
2608
|
+
})));
|
|
2609
|
+
await this.closeTaskCandidates(manager, openTasks.map((task) => task.id), readCandidateStatusForClosedTask(taskStatus));
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
async consumeJoinedTokens(manager, tokens) {
|
|
2613
|
+
if (tokens.length === 0) {
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
const consumedAt = new Date();
|
|
2617
|
+
await manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity).save(tokens.map((token) => ({
|
|
2618
|
+
...token,
|
|
2619
|
+
consumedAt,
|
|
2620
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.CONSUMED,
|
|
2621
|
+
})));
|
|
2622
|
+
}
|
|
2623
|
+
async cancelAlternativeTokensForOrTrigger(manager, instance, token, targetNodeId) {
|
|
2624
|
+
const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
|
|
2625
|
+
const taskRepository = manager.getRepository(task_entity_1.TaskEntity);
|
|
2626
|
+
const consumedAt = new Date();
|
|
2627
|
+
const tokens = await tokenRepository.find({
|
|
2628
|
+
where: { instanceId: instance.id },
|
|
2629
|
+
});
|
|
2630
|
+
const alternativeTokens = tokens.filter((candidate) => candidate.id !== token.id &&
|
|
2631
|
+
(candidate.status === workflow_engine_enums_1.WorkflowTokenStatusEnum.ACTIVE ||
|
|
2632
|
+
candidate.status === workflow_engine_enums_1.WorkflowTokenStatusEnum.WAITING) &&
|
|
2633
|
+
hasPathToNode(instance.workflowSnapshot, candidate.currentNodeId, targetNodeId));
|
|
2634
|
+
if (alternativeTokens.length === 0) {
|
|
2635
|
+
return;
|
|
2636
|
+
}
|
|
2637
|
+
const alternativeTokenIds = alternativeTokens.map((candidate) => candidate.id);
|
|
2638
|
+
const cancellableTasks = (await taskRepository.find({
|
|
2639
|
+
where: { instanceId: instance.id },
|
|
2640
|
+
})).filter((task) => alternativeTokenIds.includes(task.tokenId) &&
|
|
2641
|
+
(task.status === workflow_engine_enums_1.TaskStatusEnum.PENDING ||
|
|
2642
|
+
task.status === workflow_engine_enums_1.TaskStatusEnum.IN_PROGRESS));
|
|
2643
|
+
await tokenRepository.save(alternativeTokens.map((candidate) => ({
|
|
2644
|
+
...candidate,
|
|
2645
|
+
consumedAt,
|
|
2646
|
+
status: workflow_engine_enums_1.WorkflowTokenStatusEnum.CONSUMED,
|
|
2647
|
+
})));
|
|
2648
|
+
if (cancellableTasks.length) {
|
|
2649
|
+
await taskRepository.save(cancellableTasks.map((task) => Object.assign(new task_entity_1.TaskEntity(), task, {
|
|
2650
|
+
completedAt: consumedAt,
|
|
2651
|
+
status: workflow_engine_enums_1.TaskStatusEnum.CANCELLED,
|
|
2652
|
+
})));
|
|
2653
|
+
await this.closeTaskCandidates(manager, cancellableTasks.map((task) => task.id), workflow_engine_enums_1.TaskCandidateStatusEnum.SUPERSEDED);
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
async closeTaskCandidates(manager, taskIds, status) {
|
|
2657
|
+
if (taskIds.length === 0) {
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
const candidateRepository = manager.getRepository(task_candidate_entity_1.TaskCandidateEntity);
|
|
2661
|
+
const candidates = (await candidateRepository.find({ where: { taskId: (0, typeorm_2.In)([...taskIds]) } })).filter((candidate) => candidate.status === workflow_engine_enums_1.TaskCandidateStatusEnum.PENDING ||
|
|
2662
|
+
candidate.status === workflow_engine_enums_1.TaskCandidateStatusEnum.CLAIMED);
|
|
2663
|
+
if (candidates.length === 0) {
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
await candidateRepository.save(candidates.map((candidate) => Object.assign(new task_candidate_entity_1.TaskCandidateEntity(), candidate, { status })));
|
|
2667
|
+
}
|
|
2668
|
+
async getTemplateOrThrow(templateId) {
|
|
2669
|
+
const template = await this.approvalTemplateRepository.findOne({
|
|
2670
|
+
where: { deletedAt: (0, typeorm_2.IsNull)(), id: templateId },
|
|
2671
|
+
});
|
|
2672
|
+
if (!template) {
|
|
2673
|
+
throw new common_1.NotFoundException(`Approval template ${templateId} was not found`);
|
|
2674
|
+
}
|
|
2675
|
+
return template;
|
|
2676
|
+
}
|
|
2677
|
+
async getPublishedTemplateVersionOrThrow(versionId) {
|
|
2678
|
+
const version = await this.approvalTemplateVersionRepository.findOne({
|
|
2679
|
+
where: { id: versionId },
|
|
2680
|
+
});
|
|
2681
|
+
if (!version) {
|
|
2682
|
+
throw new common_1.NotFoundException(`Approval template version ${versionId} was not found`);
|
|
2683
|
+
}
|
|
2684
|
+
if (version.status !== template_enums_1.ApprovalTemplateVersionStatusEnum.PUBLISHED) {
|
|
2685
|
+
throw new common_1.ConflictException('Approval template current version must be published');
|
|
2686
|
+
}
|
|
2687
|
+
return version;
|
|
2688
|
+
}
|
|
2689
|
+
async getPublishedFormVersionOrThrow(versionId) {
|
|
2690
|
+
if (!versionId) {
|
|
2691
|
+
throw new common_1.ConflictException('Approval template version does not bind a form version');
|
|
2692
|
+
}
|
|
2693
|
+
const version = await this.formDefinitionVersionRepository.findOne({
|
|
2694
|
+
where: { id: versionId },
|
|
2695
|
+
});
|
|
2696
|
+
if (!version) {
|
|
2697
|
+
throw new common_1.NotFoundException(`Form definition version ${versionId} was not found`);
|
|
2698
|
+
}
|
|
2699
|
+
if (version.status !== form_enums_1.FormDefinitionVersionStatusEnum.PUBLISHED) {
|
|
2700
|
+
throw new common_1.ConflictException('Approval template must bind a published form version');
|
|
2701
|
+
}
|
|
2702
|
+
return version;
|
|
2703
|
+
}
|
|
2704
|
+
};
|
|
2705
|
+
exports.WorkflowEngineService = WorkflowEngineService;
|
|
2706
|
+
exports.WorkflowEngineService = WorkflowEngineService = tslib_1.__decorate([
|
|
2707
|
+
(0, common_1.Injectable)(),
|
|
2708
|
+
tslib_1.__param(0, (0, typeorm_1.InjectRepository)(approval_instance_entity_1.ApprovalInstanceEntity)),
|
|
2709
|
+
tslib_1.__param(1, (0, typeorm_1.InjectRepository)(workflow_token_entity_1.WorkflowTokenEntity)),
|
|
2710
|
+
tslib_1.__param(2, (0, typeorm_1.InjectRepository)(task_entity_1.TaskEntity)),
|
|
2711
|
+
tslib_1.__param(3, (0, typeorm_1.InjectRepository)(task_candidate_entity_1.TaskCandidateEntity)),
|
|
2712
|
+
tslib_1.__param(4, (0, typeorm_1.InjectRepository)(task_decision_entity_1.TaskDecisionEntity)),
|
|
2713
|
+
tslib_1.__param(5, (0, typeorm_1.InjectRepository)(notification_entity_1.NotificationEntity)),
|
|
2714
|
+
tslib_1.__param(6, (0, typeorm_1.InjectRepository)(activity_log_entity_1.ActivityLogEntity)),
|
|
2715
|
+
tslib_1.__param(7, (0, typeorm_1.InjectRepository)(adhoc_directive_entity_1.AdhocDirectiveEntity)),
|
|
2716
|
+
tslib_1.__param(8, (0, typeorm_1.InjectRepository)(approval_template_entity_1.ApprovalTemplateEntity)),
|
|
2717
|
+
tslib_1.__param(9, (0, typeorm_1.InjectRepository)(approval_template_version_entity_1.ApprovalTemplateVersionEntity)),
|
|
2718
|
+
tslib_1.__param(10, (0, typeorm_1.InjectRepository)(form_definition_version_entity_1.FormDefinitionVersionEntity)),
|
|
2719
|
+
tslib_1.__param(16, (0, common_1.Optional)()),
|
|
2720
|
+
tslib_1.__param(16, (0, common_1.Inject)(workflow_service_task_dispatcher_token_1.BPM_WORKFLOW_SERVICE_TASK_DISPATCHER)),
|
|
2721
|
+
tslib_1.__metadata("design:paramtypes", [typeorm_2.Repository,
|
|
2722
|
+
typeorm_2.Repository,
|
|
2723
|
+
typeorm_2.Repository,
|
|
2724
|
+
typeorm_2.Repository,
|
|
2725
|
+
typeorm_2.Repository,
|
|
2726
|
+
typeorm_2.Repository,
|
|
2727
|
+
typeorm_2.Repository,
|
|
2728
|
+
typeorm_2.Repository,
|
|
2729
|
+
typeorm_2.Repository,
|
|
2730
|
+
typeorm_2.Repository,
|
|
2731
|
+
typeorm_2.Repository,
|
|
2732
|
+
attachment_service_1.AttachmentService,
|
|
2733
|
+
condition_service_1.ConditionService,
|
|
2734
|
+
delegation_service_1.DelegationService,
|
|
2735
|
+
notification_service_1.NotificationService,
|
|
2736
|
+
signature_service_1.SignatureService, Object])
|
|
2737
|
+
], WorkflowEngineService);
|
|
2738
|
+
function parseJsonObject(value, label) {
|
|
2739
|
+
try {
|
|
2740
|
+
const parsedValue = JSON.parse(value);
|
|
2741
|
+
if (isRecord(parsedValue)) {
|
|
2742
|
+
return parsedValue;
|
|
2743
|
+
}
|
|
2744
|
+
throw new common_1.BadRequestException(`${label} must be a JSON object`);
|
|
2745
|
+
}
|
|
2746
|
+
catch (error) {
|
|
2747
|
+
if (error instanceof common_1.BadRequestException) {
|
|
2748
|
+
throw error;
|
|
2749
|
+
}
|
|
2750
|
+
throw new common_1.BadRequestException(error instanceof Error ? error.message : `${label} is invalid JSON`);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
function readDefaultInitiatorMetadataSnapshot(memberId) {
|
|
2754
|
+
return {
|
|
2755
|
+
customFields: {},
|
|
2756
|
+
memberId,
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
function parseWorkflowDefinition(value) {
|
|
2760
|
+
try {
|
|
2761
|
+
const parsedValue = JSON.parse(value);
|
|
2762
|
+
if (isRecord(parsedValue) &&
|
|
2763
|
+
Array.isArray(parsedValue.nodes) &&
|
|
2764
|
+
Array.isArray(parsedValue.edges) &&
|
|
2765
|
+
isRecord(parsedValue.meta)) {
|
|
2766
|
+
return parsedValue;
|
|
2767
|
+
}
|
|
2768
|
+
throw new common_1.BadRequestException('workflowDefinitionJson must be a workflow definition object');
|
|
2769
|
+
}
|
|
2770
|
+
catch (error) {
|
|
2771
|
+
if (error instanceof common_1.BadRequestException) {
|
|
2772
|
+
throw error;
|
|
2773
|
+
}
|
|
2774
|
+
throw new common_1.BadRequestException(error instanceof Error
|
|
2775
|
+
? error.message
|
|
2776
|
+
: 'workflowDefinitionJson is invalid JSON');
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
function buildInitiatorPolicyContext(initiatorMetadataSnapshot) {
|
|
2780
|
+
return {
|
|
2781
|
+
env: {
|
|
2782
|
+
now: new Date().toISOString(),
|
|
2783
|
+
},
|
|
2784
|
+
initiator: initiatorMetadataSnapshot,
|
|
2785
|
+
subject: initiatorMetadataSnapshot,
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
function buildWorkflowExpressionContext(instance) {
|
|
2789
|
+
return {
|
|
2790
|
+
env: {
|
|
2791
|
+
now: new Date().toISOString(),
|
|
2792
|
+
},
|
|
2793
|
+
form: instance.formData,
|
|
2794
|
+
formData: instance.formData,
|
|
2795
|
+
initiator: {
|
|
2796
|
+
...instance.initiatorMetadataSnapshot,
|
|
2797
|
+
memberId: instance.initiatorMemberId,
|
|
2798
|
+
},
|
|
2799
|
+
instance: {
|
|
2800
|
+
id: instance.id,
|
|
2801
|
+
state: instance.state,
|
|
2802
|
+
templateId: instance.templateId,
|
|
2803
|
+
templateVersionId: instance.templateVersionId,
|
|
2804
|
+
title: instance.title,
|
|
2805
|
+
},
|
|
2806
|
+
};
|
|
2807
|
+
}
|
|
2808
|
+
function readDelegationSteps(value) {
|
|
2809
|
+
return value
|
|
2810
|
+
.map((step) => {
|
|
2811
|
+
const from = readStringValue(step.from);
|
|
2812
|
+
const to = readStringValue(step.to);
|
|
2813
|
+
const reason = readStringValue(step.reason);
|
|
2814
|
+
if (!from || !to || !reason) {
|
|
2815
|
+
return null;
|
|
2816
|
+
}
|
|
2817
|
+
return {
|
|
2818
|
+
from,
|
|
2819
|
+
reason,
|
|
2820
|
+
ruleId: readStringValue(step.ruleId),
|
|
2821
|
+
to,
|
|
2822
|
+
};
|
|
2823
|
+
})
|
|
2824
|
+
.filter((step) => step !== null);
|
|
2825
|
+
}
|
|
2826
|
+
function readStringValue(value) {
|
|
2827
|
+
return typeof value === 'string' && value.trim() ? value : null;
|
|
2828
|
+
}
|
|
2829
|
+
function buildDryRunExpressionContext(input, lastDecision) {
|
|
2830
|
+
return {
|
|
2831
|
+
env: {
|
|
2832
|
+
now: new Date().toISOString(),
|
|
2833
|
+
},
|
|
2834
|
+
form: input.formData,
|
|
2835
|
+
formData: input.formData,
|
|
2836
|
+
initiator: {
|
|
2837
|
+
...input.initiatorMetadataSnapshot,
|
|
2838
|
+
memberId: input.initiatorMemberId,
|
|
2839
|
+
},
|
|
2840
|
+
instance: {
|
|
2841
|
+
id: SYSTEM_DRY_RUN_INSTANCE_ID,
|
|
2842
|
+
state: workflow_engine_enums_1.ApprovalInstanceStateEnum.RUNNING,
|
|
2843
|
+
title: 'Dry Run',
|
|
2844
|
+
},
|
|
2845
|
+
lastDecision,
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
function isRecord(value) {
|
|
2849
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2850
|
+
}
|
|
2851
|
+
function readWorkflowNodeOrThrow(workflow, nodeId) {
|
|
2852
|
+
const node = workflow.nodes.find((candidate) => candidate.id === nodeId);
|
|
2853
|
+
if (!node) {
|
|
2854
|
+
throw new common_1.ConflictException(`Workflow node ${nodeId} was not found`);
|
|
2855
|
+
}
|
|
2856
|
+
return node;
|
|
2857
|
+
}
|
|
2858
|
+
function readOutgoingEdgesOrThrow(workflow, nodeId) {
|
|
2859
|
+
const edges = workflow.edges.filter((edge) => edge.source === nodeId);
|
|
2860
|
+
if (edges.length === 0) {
|
|
2861
|
+
throw new common_1.ConflictException(`Workflow node ${nodeId} has no outgoing edge`);
|
|
2862
|
+
}
|
|
2863
|
+
return edges;
|
|
2864
|
+
}
|
|
2865
|
+
function readIncomingEdges(workflow, nodeId) {
|
|
2866
|
+
return workflow.edges.filter((edge) => edge.target === nodeId);
|
|
2867
|
+
}
|
|
2868
|
+
function hasPathToNode(workflow, sourceNodeId, targetNodeId) {
|
|
2869
|
+
return visitPathToNode(workflow, sourceNodeId, targetNodeId, new Set());
|
|
2870
|
+
}
|
|
2871
|
+
function visitPathToNode(workflow, sourceNodeId, targetNodeId, visitedNodeIds) {
|
|
2872
|
+
if (sourceNodeId === targetNodeId) {
|
|
2873
|
+
return true;
|
|
2874
|
+
}
|
|
2875
|
+
if (visitedNodeIds.has(sourceNodeId)) {
|
|
2876
|
+
return false;
|
|
2877
|
+
}
|
|
2878
|
+
const nextVisitedNodeIds = new Set([...visitedNodeIds, sourceNodeId]);
|
|
2879
|
+
return workflow.edges
|
|
2880
|
+
.filter((edge) => edge.source === sourceNodeId)
|
|
2881
|
+
.some((edge) => visitPathToNode(workflow, edge.target, targetNodeId, nextVisitedNodeIds));
|
|
2882
|
+
}
|
|
2883
|
+
function readReturnTargetNodeId(workflow, currentNodeId, requestedNodeId) {
|
|
2884
|
+
const currentNode = readWorkflowNodeOrThrow(workflow, currentNodeId);
|
|
2885
|
+
if (currentNode.type !== 'userTask') {
|
|
2886
|
+
throw new common_1.ConflictException(`Workflow node ${currentNodeId} does not support return decisions`);
|
|
2887
|
+
}
|
|
2888
|
+
if (!currentNode.data.returnBehavior.allowReturn) {
|
|
2889
|
+
throw new common_1.ConflictException(`User task ${currentNodeId} does not allow return decisions`);
|
|
2890
|
+
}
|
|
2891
|
+
const trimmedRequestedNodeId = requestedNodeId?.trim() || null;
|
|
2892
|
+
const defaultTargetNodeId = readDefaultReturnTargetNodeId(workflow, currentNode);
|
|
2893
|
+
if (currentNode.data.returnBehavior.allowedTargets === 'ANY') {
|
|
2894
|
+
if (!trimmedRequestedNodeId) {
|
|
2895
|
+
throw new common_1.BadRequestException('returnToNodeId is required');
|
|
2896
|
+
}
|
|
2897
|
+
readWorkflowNodeOrThrow(workflow, trimmedRequestedNodeId);
|
|
2898
|
+
return trimmedRequestedNodeId;
|
|
2899
|
+
}
|
|
2900
|
+
if (trimmedRequestedNodeId &&
|
|
2901
|
+
trimmedRequestedNodeId !== defaultTargetNodeId) {
|
|
2902
|
+
throw new common_1.ConflictException(`User task ${currentNodeId} can only return to ${defaultTargetNodeId}`);
|
|
2903
|
+
}
|
|
2904
|
+
return defaultTargetNodeId;
|
|
2905
|
+
}
|
|
2906
|
+
function readDefaultReturnTargetNodeId(workflow, currentNode) {
|
|
2907
|
+
if (currentNode.data.returnBehavior.allowedTargets === 'INITIATOR') {
|
|
2908
|
+
const startNode = workflow.nodes.find((node) => node.type === 'startEvent');
|
|
2909
|
+
if (!startNode) {
|
|
2910
|
+
throw new common_1.ConflictException('Workflow does not include a startEvent');
|
|
2911
|
+
}
|
|
2912
|
+
return startNode.id;
|
|
2913
|
+
}
|
|
2914
|
+
const previousNodeId = readIncomingEdges(workflow, currentNode.id)[0]?.source;
|
|
2915
|
+
if (!previousNodeId) {
|
|
2916
|
+
throw new common_1.ConflictException(`User task ${currentNode.id} does not have a previous node to return to`);
|
|
2917
|
+
}
|
|
2918
|
+
return previousNodeId;
|
|
2919
|
+
}
|
|
2920
|
+
function readReturnResubmitStrategy(task, instance) {
|
|
2921
|
+
const node = readWorkflowNodeOrThrow(instance.workflowSnapshot, task.nodeId);
|
|
2922
|
+
if (node.type !== 'userTask') {
|
|
2923
|
+
return 'RESTART';
|
|
2924
|
+
}
|
|
2925
|
+
return node.data.returnBehavior.resubmitStrategy ?? 'RESTART';
|
|
2926
|
+
}
|
|
2927
|
+
function readReturnResubmitStrategyFromPayload(payload) {
|
|
2928
|
+
return payload.resubmitStrategy === 'FROM_RETURN_POINT'
|
|
2929
|
+
? 'FROM_RETURN_POINT'
|
|
2930
|
+
: 'RESTART';
|
|
2931
|
+
}
|
|
2932
|
+
function readStringPayloadValue(payload, key) {
|
|
2933
|
+
const value = payload[key];
|
|
2934
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
2935
|
+
}
|
|
2936
|
+
function readNodeEntryCondition(node) {
|
|
2937
|
+
if (node.type === 'userTask' || node.type === 'serviceTask') {
|
|
2938
|
+
return node.data.entryCondition?.trim() || null;
|
|
2939
|
+
}
|
|
2940
|
+
return null;
|
|
2941
|
+
}
|
|
2942
|
+
function edgeHasCondition(edge) {
|
|
2943
|
+
return Boolean(edge.data.condition?.trim() ||
|
|
2944
|
+
(edge.data.conditionFieldKey && edge.data.conditionOperator));
|
|
2945
|
+
}
|
|
2946
|
+
function readMemberIdFromValue(value, label) {
|
|
2947
|
+
return readMemberIdsFromValue(value, label)[0] ?? '';
|
|
2948
|
+
}
|
|
2949
|
+
function readMemberIdsFromValue(value, label) {
|
|
2950
|
+
if (typeof value === 'string' && value.trim()) {
|
|
2951
|
+
return [value.trim()];
|
|
2952
|
+
}
|
|
2953
|
+
if (Array.isArray(value)) {
|
|
2954
|
+
const memberIds = value
|
|
2955
|
+
.filter((candidate) => typeof candidate === 'string' && Boolean(candidate.trim()))
|
|
2956
|
+
.map((candidate) => candidate.trim());
|
|
2957
|
+
if (memberIds.length > 0) {
|
|
2958
|
+
return uniqueTexts(memberIds);
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
throw new common_1.ConflictException(`${label} did not resolve to a member id`);
|
|
2962
|
+
}
|
|
2963
|
+
function uniqueTexts(values) {
|
|
2964
|
+
return [...new Set(values.filter((value) => value.trim()))];
|
|
2965
|
+
}
|
|
2966
|
+
function uniqueRuntimeCandidates(candidates) {
|
|
2967
|
+
const seen = new Set();
|
|
2968
|
+
return candidates.filter((candidate) => {
|
|
2969
|
+
if (seen.has(candidate.memberId)) {
|
|
2970
|
+
return false;
|
|
2971
|
+
}
|
|
2972
|
+
seen.add(candidate.memberId);
|
|
2973
|
+
return true;
|
|
2974
|
+
});
|
|
2975
|
+
}
|
|
2976
|
+
function uniqueTasksById(tasks) {
|
|
2977
|
+
const seen = new Set();
|
|
2978
|
+
return tasks.filter((task) => {
|
|
2979
|
+
if (seen.has(task.id)) {
|
|
2980
|
+
return false;
|
|
2981
|
+
}
|
|
2982
|
+
seen.add(task.id);
|
|
2983
|
+
return true;
|
|
2984
|
+
});
|
|
2985
|
+
}
|
|
2986
|
+
function normalizeText(value) {
|
|
2987
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
2988
|
+
}
|
|
2989
|
+
function readPageOffset(page, pageSize) {
|
|
2990
|
+
const normalizedPageSize = normalizePageSize(pageSize);
|
|
2991
|
+
return (normalizePage(page) - 1) * normalizedPageSize;
|
|
2992
|
+
}
|
|
2993
|
+
function shouldPaginateList(options) {
|
|
2994
|
+
return (typeof options.page !== 'undefined' ||
|
|
2995
|
+
typeof options.pageSize !== 'undefined');
|
|
2996
|
+
}
|
|
2997
|
+
function normalizePage(value) {
|
|
2998
|
+
return Number.isInteger(value) && Number(value) > 0 ? Number(value) : 1;
|
|
2999
|
+
}
|
|
3000
|
+
function normalizePageSize(value) {
|
|
3001
|
+
if (!Number.isInteger(value) || Number(value) <= 0) {
|
|
3002
|
+
return 20;
|
|
3003
|
+
}
|
|
3004
|
+
return Math.min(Number(value), 100);
|
|
3005
|
+
}
|
|
3006
|
+
function applyApprovalInstanceSearchFilter(queryBuilder, searchTextValue) {
|
|
3007
|
+
const searchText = normalizeText(searchTextValue)?.toLocaleLowerCase();
|
|
3008
|
+
if (!searchText) {
|
|
3009
|
+
return;
|
|
3010
|
+
}
|
|
3011
|
+
queryBuilder.andWhere([
|
|
3012
|
+
'(',
|
|
3013
|
+
'LOWER("approvalInstance"."title") LIKE :approvalInstanceSearchText',
|
|
3014
|
+
'OR LOWER("approvalInstance"."initiator_member_id") LIKE :approvalInstanceSearchText',
|
|
3015
|
+
'OR LOWER(CAST("approvalInstance"."id" AS text)) LIKE :approvalInstanceSearchText',
|
|
3016
|
+
')',
|
|
3017
|
+
].join(' '), {
|
|
3018
|
+
approvalInstanceSearchText: `%${escapeLikePattern(searchText)}%`,
|
|
3019
|
+
});
|
|
3020
|
+
}
|
|
3021
|
+
function escapeLikePattern(value) {
|
|
3022
|
+
return value
|
|
3023
|
+
.replace(/\\/gu, '\\\\')
|
|
3024
|
+
.replace(/%/gu, '\\%')
|
|
3025
|
+
.replace(/_/gu, '\\_');
|
|
3026
|
+
}
|
|
3027
|
+
function isInstanceWithinRange(instance, options) {
|
|
3028
|
+
const fromTime = options.from?.getTime() ?? null;
|
|
3029
|
+
const toTime = options.to?.getTime() ?? null;
|
|
3030
|
+
const candidateTime = (instance.completedAt ??
|
|
3031
|
+
instance.startedAt ??
|
|
3032
|
+
instance.createdAt).getTime();
|
|
3033
|
+
return ((fromTime === null || candidateTime >= fromTime) &&
|
|
3034
|
+
(toTime === null || candidateTime <= toTime));
|
|
3035
|
+
}
|
|
3036
|
+
function readDecisionPolicySnapshot(task) {
|
|
3037
|
+
const value = task.decisionPolicySnapshot;
|
|
3038
|
+
if (isRecord(value) &&
|
|
3039
|
+
typeof value.type === 'string' &&
|
|
3040
|
+
(value.type === 'SINGLE' ||
|
|
3041
|
+
value.type === 'SEQUENTIAL' ||
|
|
3042
|
+
value.type === 'PARALLEL_ALL' ||
|
|
3043
|
+
value.type === 'PARALLEL_ANY')) {
|
|
3044
|
+
return { type: value.type };
|
|
3045
|
+
}
|
|
3046
|
+
if (isRecord(value) &&
|
|
3047
|
+
value.type === 'QUORUM' &&
|
|
3048
|
+
typeof value.threshold === 'number' &&
|
|
3049
|
+
(value.thresholdType === 'COUNT' || value.thresholdType === 'PERCENTAGE')) {
|
|
3050
|
+
return {
|
|
3051
|
+
threshold: value.threshold,
|
|
3052
|
+
thresholdType: value.thresholdType,
|
|
3053
|
+
type: 'QUORUM',
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
return { type: 'SINGLE' };
|
|
3057
|
+
}
|
|
3058
|
+
function shouldCompleteTaskAfterDecision({ action, candidates, decisionPolicy, }) {
|
|
3059
|
+
if (action !== workflow_engine_enums_1.TaskDecisionActionEnum.APPROVED) {
|
|
3060
|
+
return true;
|
|
3061
|
+
}
|
|
3062
|
+
if (decisionPolicy.type === 'SINGLE' ||
|
|
3063
|
+
decisionPolicy.type === 'PARALLEL_ANY') {
|
|
3064
|
+
return true;
|
|
3065
|
+
}
|
|
3066
|
+
const completedCount = candidates.filter((candidate) => candidate.status === workflow_engine_enums_1.TaskCandidateStatusEnum.COMPLETED).length;
|
|
3067
|
+
const totalCount = Math.max(candidates.length, 1);
|
|
3068
|
+
if (decisionPolicy.type === 'PARALLEL_ALL' ||
|
|
3069
|
+
decisionPolicy.type === 'SEQUENTIAL') {
|
|
3070
|
+
return completedCount >= totalCount;
|
|
3071
|
+
}
|
|
3072
|
+
const threshold = decisionPolicy.thresholdType === 'PERCENTAGE'
|
|
3073
|
+
? Math.ceil((totalCount * decisionPolicy.threshold) / 100)
|
|
3074
|
+
: decisionPolicy.threshold;
|
|
3075
|
+
return completedCount >= Math.max(threshold, 1);
|
|
3076
|
+
}
|
|
3077
|
+
function readValueAtPath(value, path) {
|
|
3078
|
+
return path
|
|
3079
|
+
.split('.')
|
|
3080
|
+
.filter((segment) => segment.trim())
|
|
3081
|
+
.reduce((currentValue, segment) => {
|
|
3082
|
+
if (!isRecord(currentValue)) {
|
|
3083
|
+
return undefined;
|
|
3084
|
+
}
|
|
3085
|
+
return currentValue[segment];
|
|
3086
|
+
}, value);
|
|
3087
|
+
}
|
|
3088
|
+
function writeValueAtPath(value, path, nextValue) {
|
|
3089
|
+
const segments = normalizeFormFieldPath(path);
|
|
3090
|
+
if (segments.length === 0) {
|
|
3091
|
+
return value;
|
|
3092
|
+
}
|
|
3093
|
+
return writeNestedValue(value, segments, nextValue);
|
|
3094
|
+
}
|
|
3095
|
+
function writeNestedValue(value, segments, nextValue) {
|
|
3096
|
+
const [currentSegment, ...remainingSegments] = segments;
|
|
3097
|
+
if (!currentSegment) {
|
|
3098
|
+
return value;
|
|
3099
|
+
}
|
|
3100
|
+
if (remainingSegments.length === 0) {
|
|
3101
|
+
return { ...value, [currentSegment]: nextValue };
|
|
3102
|
+
}
|
|
3103
|
+
const currentValue = value[currentSegment];
|
|
3104
|
+
const nestedValue = isRecord(currentValue) ? currentValue : {};
|
|
3105
|
+
return {
|
|
3106
|
+
...value,
|
|
3107
|
+
[currentSegment]: writeNestedValue(nestedValue, remainingSegments, nextValue),
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
function normalizeFormFieldPath(path) {
|
|
3111
|
+
const segments = path
|
|
3112
|
+
.split('.')
|
|
3113
|
+
.map((segment) => segment.trim())
|
|
3114
|
+
.filter(Boolean);
|
|
3115
|
+
if (segments[0] === 'form' || segments[0] === 'formData') {
|
|
3116
|
+
return segments.slice(1);
|
|
3117
|
+
}
|
|
3118
|
+
return segments;
|
|
3119
|
+
}
|
|
3120
|
+
async function executeWebhookServiceAction(serviceTaskDispatcher, action, payload) {
|
|
3121
|
+
try {
|
|
3122
|
+
return await serviceTaskDispatcher.dispatchWebhook({
|
|
3123
|
+
headers: action.headers,
|
|
3124
|
+
payload,
|
|
3125
|
+
url: action.url,
|
|
3126
|
+
});
|
|
3127
|
+
}
|
|
3128
|
+
catch (error) {
|
|
3129
|
+
return {
|
|
3130
|
+
error: error instanceof Error ? error.message : 'Unknown webhook error',
|
|
3131
|
+
ok: false,
|
|
3132
|
+
status: null,
|
|
3133
|
+
};
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
async function executeAdhocWebhookDispatch(serviceTaskDispatcher, target, payload) {
|
|
3137
|
+
if (!target.webhookUrl) {
|
|
3138
|
+
return { error: 'Webhook URL is missing', ok: false, status: null };
|
|
3139
|
+
}
|
|
3140
|
+
try {
|
|
3141
|
+
return await serviceTaskDispatcher.dispatchWebhook({
|
|
3142
|
+
headers: target.webhookHeaders,
|
|
3143
|
+
payload,
|
|
3144
|
+
url: target.webhookUrl,
|
|
3145
|
+
});
|
|
3146
|
+
}
|
|
3147
|
+
catch (error) {
|
|
3148
|
+
return {
|
|
3149
|
+
error: error instanceof Error ? error.message : 'Unknown webhook error',
|
|
3150
|
+
ok: false,
|
|
3151
|
+
status: null,
|
|
3152
|
+
};
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
function buildAdhocTargetValue(input) {
|
|
3156
|
+
if (input.kind === adhoc_enums_1.AdhocTargetKindEnum.MEMBER) {
|
|
3157
|
+
const memberIds = uniqueTexts([...(input.memberIds ?? [])]);
|
|
3158
|
+
if (memberIds.length === 0) {
|
|
3159
|
+
throw new common_1.BadRequestException('memberIds is required for a MEMBER ad-hoc target');
|
|
3160
|
+
}
|
|
3161
|
+
return { kind: input.kind, memberIds };
|
|
3162
|
+
}
|
|
3163
|
+
if (input.kind === adhoc_enums_1.AdhocTargetKindEnum.POSITION) {
|
|
3164
|
+
const positionId = input.positionId?.trim();
|
|
3165
|
+
if (!positionId) {
|
|
3166
|
+
throw new common_1.BadRequestException('positionId is required for a POSITION ad-hoc target');
|
|
3167
|
+
}
|
|
3168
|
+
return { kind: input.kind, positionId };
|
|
3169
|
+
}
|
|
3170
|
+
if (input.kind === adhoc_enums_1.AdhocTargetKindEnum.ORG_UNIT_MEMBER) {
|
|
3171
|
+
const orgUnitId = input.orgUnitId?.trim();
|
|
3172
|
+
if (!orgUnitId) {
|
|
3173
|
+
throw new common_1.BadRequestException('orgUnitId is required for an ORG_UNIT_MEMBER ad-hoc target');
|
|
3174
|
+
}
|
|
3175
|
+
return {
|
|
3176
|
+
includeDescendants: Boolean(input.includeDescendants),
|
|
3177
|
+
kind: input.kind,
|
|
3178
|
+
orgUnitId,
|
|
3179
|
+
};
|
|
3180
|
+
}
|
|
3181
|
+
const webhookUrl = input.webhookUrl?.trim();
|
|
3182
|
+
if (!webhookUrl) {
|
|
3183
|
+
throw new common_1.BadRequestException('webhookUrl is required for a WEBHOOK ad-hoc target');
|
|
3184
|
+
}
|
|
3185
|
+
return {
|
|
3186
|
+
kind: input.kind,
|
|
3187
|
+
webhookHeaders: input.webhookHeadersJson
|
|
3188
|
+
? parseAdhocWebhookHeaders(input.webhookHeadersJson)
|
|
3189
|
+
: undefined,
|
|
3190
|
+
webhookUrl,
|
|
3191
|
+
};
|
|
3192
|
+
}
|
|
3193
|
+
function parseAdhocWebhookHeaders(json) {
|
|
3194
|
+
try {
|
|
3195
|
+
const parsed = JSON.parse(json);
|
|
3196
|
+
if (!isRecord(parsed)) {
|
|
3197
|
+
throw new common_1.BadRequestException('webhookHeadersJson must be a JSON object of string values');
|
|
3198
|
+
}
|
|
3199
|
+
return Object.entries(parsed).reduce((accumulator, [key, value]) => ({
|
|
3200
|
+
...accumulator,
|
|
3201
|
+
...(typeof value === 'string' ? { [key]: value } : {}),
|
|
3202
|
+
}), {});
|
|
3203
|
+
}
|
|
3204
|
+
catch (error) {
|
|
3205
|
+
if (error instanceof common_1.BadRequestException) {
|
|
3206
|
+
throw error;
|
|
3207
|
+
}
|
|
3208
|
+
throw new common_1.BadRequestException('webhookHeadersJson is not valid JSON');
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
function readAdhocTargetValue(directive) {
|
|
3212
|
+
const value = directive.targetValue;
|
|
3213
|
+
const kind = directive.targetKind;
|
|
3214
|
+
if (kind === adhoc_enums_1.AdhocTargetKindEnum.MEMBER) {
|
|
3215
|
+
return {
|
|
3216
|
+
kind,
|
|
3217
|
+
memberIds: Array.isArray(value.memberIds)
|
|
3218
|
+
? value.memberIds.filter((memberId) => typeof memberId === 'string')
|
|
3219
|
+
: [],
|
|
3220
|
+
};
|
|
3221
|
+
}
|
|
3222
|
+
if (kind === adhoc_enums_1.AdhocTargetKindEnum.POSITION) {
|
|
3223
|
+
return {
|
|
3224
|
+
kind,
|
|
3225
|
+
positionId: typeof value.positionId === 'string' ? value.positionId : '',
|
|
3226
|
+
};
|
|
3227
|
+
}
|
|
3228
|
+
if (kind === adhoc_enums_1.AdhocTargetKindEnum.ORG_UNIT_MEMBER) {
|
|
3229
|
+
return {
|
|
3230
|
+
includeDescendants: Boolean(value.includeDescendants),
|
|
3231
|
+
kind,
|
|
3232
|
+
orgUnitId: typeof value.orgUnitId === 'string' ? value.orgUnitId : '',
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
return {
|
|
3236
|
+
kind,
|
|
3237
|
+
webhookHeaders: isRecord(value.webhookHeaders)
|
|
3238
|
+
? Object.entries(value.webhookHeaders).reduce((accumulator, [key, headerValue]) => ({
|
|
3239
|
+
...accumulator,
|
|
3240
|
+
...(typeof headerValue === 'string'
|
|
3241
|
+
? { [key]: headerValue }
|
|
3242
|
+
: {}),
|
|
3243
|
+
}), {})
|
|
3244
|
+
: undefined,
|
|
3245
|
+
webhookUrl: typeof value.webhookUrl === 'string' ? value.webhookUrl : '',
|
|
3246
|
+
};
|
|
3247
|
+
}
|
|
3248
|
+
function buildAdhocApproverResolver(target) {
|
|
3249
|
+
if (target.kind === adhoc_enums_1.AdhocTargetKindEnum.MEMBER) {
|
|
3250
|
+
return { memberIds: target.memberIds ?? [], type: 'DIRECT' };
|
|
3251
|
+
}
|
|
3252
|
+
if (target.kind === adhoc_enums_1.AdhocTargetKindEnum.POSITION) {
|
|
3253
|
+
return { positionId: target.positionId ?? '', type: 'POSITION' };
|
|
3254
|
+
}
|
|
3255
|
+
if (target.kind === adhoc_enums_1.AdhocTargetKindEnum.ORG_UNIT_MEMBER) {
|
|
3256
|
+
return {
|
|
3257
|
+
includeDescendants: target.includeDescendants,
|
|
3258
|
+
orgUnitId: target.orgUnitId ?? '',
|
|
3259
|
+
type: 'ORG_UNIT_MEMBER',
|
|
3260
|
+
};
|
|
3261
|
+
}
|
|
3262
|
+
throw new common_1.BadRequestException('WEBHOOK ad-hoc targets cannot be used as approvers');
|
|
3263
|
+
}
|
|
3264
|
+
function readAdhocStageOutcomeLabel(outcome) {
|
|
3265
|
+
if (outcome === 'APPROVED') {
|
|
3266
|
+
return '通過';
|
|
3267
|
+
}
|
|
3268
|
+
if (outcome === 'REJECTED') {
|
|
3269
|
+
return '拒絕';
|
|
3270
|
+
}
|
|
3271
|
+
return '退回';
|
|
3272
|
+
}
|
|
3273
|
+
function readInstanceFinalStateLabel(finalState) {
|
|
3274
|
+
if (finalState === workflow_engine_enums_1.ApprovalInstanceStateEnum.APPROVED) {
|
|
3275
|
+
return '核准';
|
|
3276
|
+
}
|
|
3277
|
+
if (finalState === workflow_engine_enums_1.ApprovalInstanceStateEnum.REJECTED) {
|
|
3278
|
+
return '拒絕';
|
|
3279
|
+
}
|
|
3280
|
+
return '取消';
|
|
3281
|
+
}
|
|
3282
|
+
function readManagerMemberIdFromInitiatorSnapshot(initiatorMetadataSnapshot, levelsUp) {
|
|
3283
|
+
const managerChain = initiatorMetadataSnapshot.managerChain;
|
|
3284
|
+
if (Array.isArray(managerChain)) {
|
|
3285
|
+
return managerChain[Math.max(levelsUp - 1, 0)];
|
|
3286
|
+
}
|
|
3287
|
+
const managerMemberIds = initiatorMetadataSnapshot.managerMemberIds;
|
|
3288
|
+
if (Array.isArray(managerMemberIds)) {
|
|
3289
|
+
return managerMemberIds[Math.max(levelsUp - 1, 0)];
|
|
3290
|
+
}
|
|
3291
|
+
const customFields = initiatorMetadataSnapshot.customFields;
|
|
3292
|
+
if (isRecord(customFields)) {
|
|
3293
|
+
return customFields.managerMemberId;
|
|
3294
|
+
}
|
|
3295
|
+
return initiatorMetadataSnapshot.managerMemberId;
|
|
3296
|
+
}
|
|
3297
|
+
function isDateRangeActive(value, date) {
|
|
3298
|
+
return (value.effectiveFrom <= date &&
|
|
3299
|
+
(!value.effectiveTo || value.effectiveTo >= date));
|
|
3300
|
+
}
|
|
3301
|
+
function compareManagerResolution(left, right) {
|
|
3302
|
+
const priorityDiff = right.priority - left.priority;
|
|
3303
|
+
if (priorityDiff !== 0) {
|
|
3304
|
+
return priorityDiff;
|
|
3305
|
+
}
|
|
3306
|
+
return (readManagerResolutionScopeRank(right.scopeType) -
|
|
3307
|
+
readManagerResolutionScopeRank(left.scopeType) ||
|
|
3308
|
+
right.effectiveFrom.localeCompare(left.effectiveFrom));
|
|
3309
|
+
}
|
|
3310
|
+
function readManagerResolutionScopeRank(scopeType) {
|
|
3311
|
+
const ranks = {
|
|
3312
|
+
[organization_enums_1.ManagerResolutionScopeTypeEnum.MEMBER]: 3,
|
|
3313
|
+
[organization_enums_1.ManagerResolutionScopeTypeEnum.ORG_UNIT]: 2,
|
|
3314
|
+
[organization_enums_1.ManagerResolutionScopeTypeEnum.POSITION]: 1,
|
|
3315
|
+
};
|
|
3316
|
+
return ranks[scopeType];
|
|
3317
|
+
}
|
|
3318
|
+
function compareMembership(left, right) {
|
|
3319
|
+
if (left.isPrimary !== right.isPrimary) {
|
|
3320
|
+
return left.isPrimary ? -1 : 1;
|
|
3321
|
+
}
|
|
3322
|
+
return right.effectiveFrom.localeCompare(left.effectiveFrom);
|
|
3323
|
+
}
|
|
3324
|
+
function toDateOnlyString(value) {
|
|
3325
|
+
return value.toISOString().slice(0, 10);
|
|
3326
|
+
}
|
|
3327
|
+
function canReadAllWorkflows(scope) {
|
|
3328
|
+
return (scope.roles.includes('BPM_ADMIN') ||
|
|
3329
|
+
scope.permissions.some((permission) => WORKFLOW_READ_ALL_PERMISSIONS.has(permission)));
|
|
3330
|
+
}
|
|
3331
|
+
function isTaskRelatedToMember(task, memberId) {
|
|
3332
|
+
return (task.assigneeMemberId === memberId ||
|
|
3333
|
+
task.originalAssigneeMemberId === memberId);
|
|
3334
|
+
}
|
|
3335
|
+
function readCandidateStatusForClosedTask(taskStatus) {
|
|
3336
|
+
if (taskStatus === workflow_engine_enums_1.TaskStatusEnum.TRANSFERRED) {
|
|
3337
|
+
return workflow_engine_enums_1.TaskCandidateStatusEnum.TRANSFERRED;
|
|
3338
|
+
}
|
|
3339
|
+
if (taskStatus === workflow_engine_enums_1.TaskStatusEnum.COMPLETED) {
|
|
3340
|
+
return workflow_engine_enums_1.TaskCandidateStatusEnum.COMPLETED;
|
|
3341
|
+
}
|
|
3342
|
+
return workflow_engine_enums_1.TaskCandidateStatusEnum.CANCELLED;
|
|
3343
|
+
}
|
|
3344
|
+
function validateSubmittedFormData(schema, formData) {
|
|
3345
|
+
const missingFields = schema.fields.filter((field) => {
|
|
3346
|
+
const fieldValue = formData[field.fieldKey];
|
|
3347
|
+
return (isSubmittedFormFieldVisible(field, schema.fields, formData) &&
|
|
3348
|
+
isSubmittedFormFieldRequired(field, schema.fields, formData) &&
|
|
3349
|
+
!isSubmittedFieldValuePresent(fieldValue));
|
|
3350
|
+
});
|
|
3351
|
+
if (missingFields.length > 0) {
|
|
3352
|
+
throw new common_1.BadRequestException(`Form data is missing required fields: ${missingFields
|
|
3353
|
+
.map((field) => field.label || field.fieldKey)
|
|
3354
|
+
.join(', ')}`);
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
function readFormDefinitionSnapshotSchema(snapshot) {
|
|
3358
|
+
const schema = snapshot.schema;
|
|
3359
|
+
if (isFormDefinitionSchema(schema)) {
|
|
3360
|
+
return schema;
|
|
3361
|
+
}
|
|
3362
|
+
throw new common_1.BadRequestException('Approval instance form schema is invalid');
|
|
3363
|
+
}
|
|
3364
|
+
function isFormDefinitionSchema(value) {
|
|
3365
|
+
return (isRecord(value) && value.schemaVersion === 1 && Array.isArray(value.fields));
|
|
3366
|
+
}
|
|
3367
|
+
function isSubmittedFormFieldVisible(field, fields, values) {
|
|
3368
|
+
return field.visibleWhen
|
|
3369
|
+
? evaluateFormConditionExpression(field.visibleWhen, fields, values, true)
|
|
3370
|
+
: true;
|
|
3371
|
+
}
|
|
3372
|
+
function isSubmittedFormFieldRequired(field, fields, values) {
|
|
3373
|
+
return (field.required ||
|
|
3374
|
+
Boolean(field.requiredWhen
|
|
3375
|
+
? evaluateFormConditionExpression(field.requiredWhen, fields, values, false)
|
|
3376
|
+
: false));
|
|
3377
|
+
}
|
|
3378
|
+
function evaluateFormConditionExpression(expression, fields, values, fallback) {
|
|
3379
|
+
const rule = parseFormConditionRule(expression);
|
|
3380
|
+
if (!rule) {
|
|
3381
|
+
return fallback;
|
|
3382
|
+
}
|
|
3383
|
+
const field = fields.find((candidate) => candidate.fieldKey === rule.fieldKey);
|
|
3384
|
+
if (!field) {
|
|
3385
|
+
return fallback;
|
|
3386
|
+
}
|
|
3387
|
+
return evaluateFormConditionRule(rule, field, values[field.fieldKey]);
|
|
3388
|
+
}
|
|
3389
|
+
function parseFormConditionRule(expression) {
|
|
3390
|
+
const match = expression
|
|
3391
|
+
.trim()
|
|
3392
|
+
.match(/^form\.([A-Za-z][A-Za-z0-9_]*)\s*(==|!=|>=|<=|>|<)\s*(.+)$/u);
|
|
3393
|
+
if (!match) {
|
|
3394
|
+
return null;
|
|
3395
|
+
}
|
|
3396
|
+
const operator = readFormConditionOperatorFromSymbol(match[2]);
|
|
3397
|
+
if (!operator) {
|
|
3398
|
+
return null;
|
|
3399
|
+
}
|
|
3400
|
+
return {
|
|
3401
|
+
fieldKey: match[1],
|
|
3402
|
+
operator,
|
|
3403
|
+
value: parseFormConditionLiteral(match[3]),
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
function evaluateFormConditionRule(rule, field, value) {
|
|
3407
|
+
if (Array.isArray(value)) {
|
|
3408
|
+
return evaluateArrayFormCondition(value, rule);
|
|
3409
|
+
}
|
|
3410
|
+
if (field.type === 'boolean') {
|
|
3411
|
+
return compareFormConditionValues(value === true ? 'true' : 'false', rule.value, rule.operator);
|
|
3412
|
+
}
|
|
3413
|
+
if (isNumberFormField(field)) {
|
|
3414
|
+
return compareNumericFormCondition(value, rule);
|
|
3415
|
+
}
|
|
3416
|
+
return compareFormConditionValues(typeof value === 'undefined' || value === null ? '' : String(value), rule.value, rule.operator);
|
|
3417
|
+
}
|
|
3418
|
+
function evaluateArrayFormCondition(value, rule) {
|
|
3419
|
+
const stringValues = value.filter((entry) => typeof entry === 'string');
|
|
3420
|
+
if (rule.operator === 'equals') {
|
|
3421
|
+
return stringValues.includes(rule.value);
|
|
3422
|
+
}
|
|
3423
|
+
if (rule.operator === 'notEquals') {
|
|
3424
|
+
return !stringValues.includes(rule.value);
|
|
3425
|
+
}
|
|
3426
|
+
return false;
|
|
3427
|
+
}
|
|
3428
|
+
function compareNumericFormCondition(value, rule) {
|
|
3429
|
+
const actualValue = typeof value === 'number' ? value : Number(value);
|
|
3430
|
+
const expectedValue = Number(rule.value);
|
|
3431
|
+
if (!Number.isFinite(actualValue) || !Number.isFinite(expectedValue)) {
|
|
3432
|
+
return false;
|
|
3433
|
+
}
|
|
3434
|
+
return compareFormConditionValues(actualValue, expectedValue, rule.operator);
|
|
3435
|
+
}
|
|
3436
|
+
function compareFormConditionValues(actualValue, expectedValue, operator) {
|
|
3437
|
+
if (operator === 'equals') {
|
|
3438
|
+
return actualValue === expectedValue;
|
|
3439
|
+
}
|
|
3440
|
+
if (operator === 'notEquals') {
|
|
3441
|
+
return actualValue !== expectedValue;
|
|
3442
|
+
}
|
|
3443
|
+
if (operator === 'greaterThan') {
|
|
3444
|
+
return actualValue > expectedValue;
|
|
3445
|
+
}
|
|
3446
|
+
if (operator === 'greaterThanOrEqual') {
|
|
3447
|
+
return actualValue >= expectedValue;
|
|
3448
|
+
}
|
|
3449
|
+
if (operator === 'lessThan') {
|
|
3450
|
+
return actualValue < expectedValue;
|
|
3451
|
+
}
|
|
3452
|
+
return actualValue <= expectedValue;
|
|
3453
|
+
}
|
|
3454
|
+
function readFormConditionOperatorFromSymbol(symbol) {
|
|
3455
|
+
const operators = {
|
|
3456
|
+
'!=': 'notEquals',
|
|
3457
|
+
'<': 'lessThan',
|
|
3458
|
+
'<=': 'lessThanOrEqual',
|
|
3459
|
+
'==': 'equals',
|
|
3460
|
+
'>': 'greaterThan',
|
|
3461
|
+
'>=': 'greaterThanOrEqual',
|
|
3462
|
+
};
|
|
3463
|
+
return symbol ? (operators[symbol] ?? null) : null;
|
|
3464
|
+
}
|
|
3465
|
+
function parseFormConditionLiteral(value) {
|
|
3466
|
+
const trimmedValue = value.trim();
|
|
3467
|
+
if ((trimmedValue.startsWith('"') && trimmedValue.endsWith('"')) ||
|
|
3468
|
+
(trimmedValue.startsWith("'") && trimmedValue.endsWith("'"))) {
|
|
3469
|
+
return trimmedValue.slice(1, -1);
|
|
3470
|
+
}
|
|
3471
|
+
return trimmedValue;
|
|
3472
|
+
}
|
|
3473
|
+
function isSubmittedFieldValuePresent(value) {
|
|
3474
|
+
if (typeof value === 'undefined' || value === null) {
|
|
3475
|
+
return false;
|
|
3476
|
+
}
|
|
3477
|
+
if (typeof value === 'string') {
|
|
3478
|
+
return value.trim().length > 0;
|
|
3479
|
+
}
|
|
3480
|
+
if (Array.isArray(value)) {
|
|
3481
|
+
return value.length > 0;
|
|
3482
|
+
}
|
|
3483
|
+
return true;
|
|
3484
|
+
}
|
|
3485
|
+
function isNumberFormField(field) {
|
|
3486
|
+
return field.type === 'number' || field.type === 'money';
|
|
3487
|
+
}
|
|
3488
|
+
function createDryRunStep(input) {
|
|
3489
|
+
return Object.assign(new workflow_dry_run_object_1.WorkflowDryRunStepObject(), {
|
|
3490
|
+
assigneeMemberId: input.assigneeMemberId,
|
|
3491
|
+
edgeDefault: input.edge ? Boolean(input.edge.data.isDefault) : null,
|
|
3492
|
+
edgeId: input.edge?.id ?? null,
|
|
3493
|
+
edgeLabel: input.edge ? readDryRunEdgeLabel(input.edge) : null,
|
|
3494
|
+
edgeMatched: input.edgeMatched,
|
|
3495
|
+
edgeReason: input.edgeReason,
|
|
3496
|
+
entryCondition: input.entryCondition,
|
|
3497
|
+
entryConditionMatched: input.entryConditionMet,
|
|
3498
|
+
id: `dry-run-step-${input.stepIndex + 1}`,
|
|
3499
|
+
message: input.message,
|
|
3500
|
+
nodeId: input.node.id,
|
|
3501
|
+
nodeLabel: input.node.data.label,
|
|
3502
|
+
nodeType: input.node.type,
|
|
3503
|
+
status: input.status,
|
|
3504
|
+
});
|
|
3505
|
+
}
|
|
3506
|
+
function readDryRunEdgeLabel(edge) {
|
|
3507
|
+
return edge.data.label?.trim() || edge.id;
|
|
3508
|
+
}
|
|
3509
|
+
function readDryRunEdgeReason(edge) {
|
|
3510
|
+
if (!edge) {
|
|
3511
|
+
return null;
|
|
3512
|
+
}
|
|
3513
|
+
if (edge.data.isDefault) {
|
|
3514
|
+
return '其他條件不符合時採用預設路徑。';
|
|
3515
|
+
}
|
|
3516
|
+
const expression = edge.data.condition?.trim();
|
|
3517
|
+
if (expression) {
|
|
3518
|
+
return `條件成立:${expression}`;
|
|
3519
|
+
}
|
|
3520
|
+
if (edge.data.conditionFieldKey && edge.data.conditionOperator) {
|
|
3521
|
+
return `條件成立:${edge.data.conditionFieldKey} ${edge.data.conditionOperator}${edge.data.conditionValue ? ` ${edge.data.conditionValue}` : ''}`;
|
|
3522
|
+
}
|
|
3523
|
+
return '無條件路徑。';
|
|
3524
|
+
}
|
|
3525
|
+
function readDryRunStepStatus(node, entryConditionMet) {
|
|
3526
|
+
if (!entryConditionMet) {
|
|
3527
|
+
return 'SKIPPED';
|
|
3528
|
+
}
|
|
3529
|
+
if (node.type === 'userTask') {
|
|
3530
|
+
return 'WAITING';
|
|
3531
|
+
}
|
|
3532
|
+
if (node.type === 'endEvent') {
|
|
3533
|
+
return 'COMPLETED';
|
|
3534
|
+
}
|
|
3535
|
+
return 'PASSED';
|
|
3536
|
+
}
|
|
3537
|
+
function readDryRunStepMessage(node, entryConditionMet) {
|
|
3538
|
+
if (!entryConditionMet) {
|
|
3539
|
+
return '進入條件不符合,略過節點。';
|
|
3540
|
+
}
|
|
3541
|
+
if (node.type === 'userTask') {
|
|
3542
|
+
return '將建立待簽任務。';
|
|
3543
|
+
}
|
|
3544
|
+
if (node.type === 'endEvent') {
|
|
3545
|
+
return `流程結束:${node.data.endState ?? 'APPROVED'}`;
|
|
3546
|
+
}
|
|
3547
|
+
return '節點條件通過。';
|
|
3548
|
+
}
|
|
3549
|
+
function mapDecisionToResolution(action) {
|
|
3550
|
+
switch (action) {
|
|
3551
|
+
case workflow_engine_enums_1.TaskDecisionActionEnum.APPROVED:
|
|
3552
|
+
return notification_enums_1.NotificationResolutionEnum.APPROVED;
|
|
3553
|
+
case workflow_engine_enums_1.TaskDecisionActionEnum.REJECTED:
|
|
3554
|
+
return notification_enums_1.NotificationResolutionEnum.REJECTED;
|
|
3555
|
+
case workflow_engine_enums_1.TaskDecisionActionEnum.RETURNED:
|
|
3556
|
+
return notification_enums_1.NotificationResolutionEnum.RETURNED;
|
|
3557
|
+
case workflow_engine_enums_1.TaskDecisionActionEnum.TRANSFERRED:
|
|
3558
|
+
return notification_enums_1.NotificationResolutionEnum.TRANSFERRED;
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
//# sourceMappingURL=workflow-engine.service.js.map
|