@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.
Files changed (657) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +1 -1
  3. package/src/index.js +18 -0
  4. package/src/index.js.map +1 -0
  5. package/src/lib/attachment/attachment-options.d.ts +44 -0
  6. package/src/lib/attachment/attachment-options.js +62 -0
  7. package/src/lib/attachment/attachment-options.js.map +1 -0
  8. package/src/lib/attachment/attachment-storage.provider.d.ts +4 -0
  9. package/src/lib/attachment/attachment-storage.provider.js +35 -0
  10. package/src/lib/attachment/attachment-storage.provider.js.map +1 -0
  11. package/src/lib/attachment/{attachment-storage.token.ts → attachment-storage.token.d.ts} +1 -3
  12. package/src/lib/attachment/attachment-storage.token.js +5 -0
  13. package/src/lib/attachment/attachment-storage.token.js.map +1 -0
  14. package/src/lib/attachment/attachment.controller.d.ts +8 -0
  15. package/src/lib/attachment/attachment.controller.js +38 -0
  16. package/src/lib/attachment/attachment.controller.js.map +1 -0
  17. package/src/lib/attachment/attachment.entity.d.ts +14 -0
  18. package/src/lib/attachment/attachment.entity.js +74 -0
  19. package/src/lib/attachment/attachment.entity.js.map +1 -0
  20. package/src/lib/attachment/attachment.module.d.ts +27 -0
  21. package/src/lib/attachment/attachment.module.js +103 -0
  22. package/src/lib/attachment/attachment.module.js.map +1 -0
  23. package/src/lib/attachment/attachment.mutations.d.ts +8 -0
  24. package/src/lib/attachment/attachment.mutations.js +35 -0
  25. package/src/lib/attachment/attachment.mutations.js.map +1 -0
  26. package/src/lib/attachment/attachment.queries.d.ts +9 -0
  27. package/src/lib/attachment/attachment.queries.js +78 -0
  28. package/src/lib/attachment/attachment.queries.js.map +1 -0
  29. package/src/lib/attachment/attachment.service.d.ts +49 -0
  30. package/src/lib/attachment/attachment.service.js +276 -0
  31. package/src/lib/attachment/attachment.service.js.map +1 -0
  32. package/src/lib/attachment/dto/upload-attachment.input.d.ts +11 -0
  33. package/src/lib/attachment/dto/upload-attachment.input.js +68 -0
  34. package/src/lib/attachment/dto/upload-attachment.input.js.map +1 -0
  35. package/src/lib/attachment/index.js +9 -0
  36. package/src/lib/attachment/index.js.map +1 -0
  37. package/src/lib/bpm/bpm-root.module.d.ts +155 -0
  38. package/src/lib/bpm/bpm-root.module.js +170 -0
  39. package/src/lib/bpm/bpm-root.module.js.map +1 -0
  40. package/src/lib/bpm/index.js +5 -0
  41. package/src/lib/bpm/index.js.map +1 -0
  42. package/src/lib/bpm-auth/bpm-auth-context.d.ts +24 -0
  43. package/src/lib/bpm-auth/bpm-auth-context.extractor.d.ts +4 -0
  44. package/src/lib/bpm-auth/bpm-auth-context.extractor.js +69 -0
  45. package/src/lib/bpm-auth/bpm-auth-context.extractor.js.map +1 -0
  46. package/src/lib/bpm-auth/bpm-auth-context.js +15 -0
  47. package/src/lib/bpm-auth/bpm-auth-context.js.map +1 -0
  48. package/src/lib/bpm-auth/bpm-auth.authorization.d.ts +12 -0
  49. package/src/lib/bpm-auth/bpm-auth.authorization.js +67 -0
  50. package/src/lib/bpm-auth/bpm-auth.authorization.js.map +1 -0
  51. package/src/lib/bpm-auth/bpm-auth.decorators.d.ts +3 -0
  52. package/src/lib/bpm-auth/bpm-auth.decorators.js +11 -0
  53. package/src/lib/bpm-auth/bpm-auth.decorators.js.map +1 -0
  54. package/src/lib/bpm-auth/bpm-auth.guard.d.ts +7 -0
  55. package/src/lib/bpm-auth/bpm-auth.guard.js +24 -0
  56. package/src/lib/bpm-auth/bpm-auth.guard.js.map +1 -0
  57. package/src/lib/bpm-auth/bpm-auth.module.d.ts +6 -0
  58. package/src/lib/bpm-auth/bpm-auth.module.js +69 -0
  59. package/src/lib/bpm-auth/bpm-auth.module.js.map +1 -0
  60. package/src/lib/bpm-auth/bpm-auth.options.d.ts +11 -0
  61. package/src/lib/bpm-auth/bpm-auth.options.js +3 -0
  62. package/src/lib/bpm-auth/bpm-auth.options.js.map +1 -0
  63. package/src/lib/bpm-auth/configurable-bpm-auth-context.accessor.d.ts +10 -0
  64. package/src/lib/bpm-auth/configurable-bpm-auth-context.accessor.js +40 -0
  65. package/src/lib/bpm-auth/configurable-bpm-auth-context.accessor.js.map +1 -0
  66. package/src/lib/bpm-auth/index.js +10 -0
  67. package/src/lib/bpm-auth/index.js.map +1 -0
  68. package/src/lib/common/filters/all-exceptions.filter.d.ts +25 -0
  69. package/src/lib/common/filters/all-exceptions.filter.js +93 -0
  70. package/src/lib/common/filters/all-exceptions.filter.js.map +1 -0
  71. package/src/lib/common/index.js +5 -0
  72. package/src/lib/common/index.js.map +1 -0
  73. package/src/lib/common/iso-duration.d.ts +1 -0
  74. package/src/lib/common/iso-duration.js +27 -0
  75. package/src/lib/common/iso-duration.js.map +1 -0
  76. package/src/lib/condition/condition.module.d.ts +2 -0
  77. package/src/lib/condition/condition.module.js +16 -0
  78. package/src/lib/condition/condition.module.js.map +1 -0
  79. package/src/lib/condition/condition.service.d.ts +16 -0
  80. package/src/lib/condition/condition.service.js +82 -0
  81. package/src/lib/condition/condition.service.js.map +1 -0
  82. package/src/lib/condition/index.js +6 -0
  83. package/src/lib/condition/index.js.map +1 -0
  84. package/src/lib/database/data-source.d.ts +4 -0
  85. package/src/lib/database/data-source.js +7 -0
  86. package/src/lib/database/data-source.js.map +1 -0
  87. package/src/lib/database/index.js +6 -0
  88. package/src/lib/database/index.js.map +1 -0
  89. package/src/lib/database/migration-runner.d.ts +1 -0
  90. package/src/lib/database/migration-runner.js +25 -0
  91. package/src/lib/database/migration-runner.js.map +1 -0
  92. package/src/lib/database/reconcile-legacy-migrations.d.ts +13 -0
  93. package/src/lib/database/reconcile-legacy-migrations.js +70 -0
  94. package/src/lib/database/reconcile-legacy-migrations.js.map +1 -0
  95. package/src/lib/database/typeorm.config.d.ts +14 -0
  96. package/src/lib/database/typeorm.config.js +135 -0
  97. package/src/lib/database/typeorm.config.js.map +1 -0
  98. package/src/lib/delegation/delegation-rule.entity.d.ts +19 -0
  99. package/src/lib/delegation/delegation-rule.entity.js +99 -0
  100. package/src/lib/delegation/delegation-rule.entity.js.map +1 -0
  101. package/src/lib/delegation/delegation.enums.d.ts +10 -0
  102. package/src/lib/delegation/delegation.enums.js +23 -0
  103. package/src/lib/delegation/delegation.enums.js.map +1 -0
  104. package/src/lib/delegation/delegation.module.d.ts +2 -0
  105. package/src/lib/delegation/delegation.module.js +22 -0
  106. package/src/lib/delegation/delegation.module.js.map +1 -0
  107. package/src/lib/delegation/delegation.mutations.d.ts +11 -0
  108. package/src/lib/delegation/delegation.mutations.js +87 -0
  109. package/src/lib/delegation/delegation.mutations.js.map +1 -0
  110. package/src/lib/delegation/delegation.queries.d.ts +10 -0
  111. package/src/lib/delegation/delegation.queries.js +91 -0
  112. package/src/lib/delegation/delegation.queries.js.map +1 -0
  113. package/src/lib/delegation/delegation.service.d.ts +56 -0
  114. package/src/lib/delegation/delegation.service.js +268 -0
  115. package/src/lib/delegation/delegation.service.js.map +1 -0
  116. package/src/lib/delegation/dto/delegation-rule.input.d.ts +24 -0
  117. package/src/lib/delegation/dto/delegation-rule.input.js +143 -0
  118. package/src/lib/delegation/dto/delegation-rule.input.js.map +1 -0
  119. package/src/lib/delegation/index.js +9 -0
  120. package/src/lib/delegation/index.js.map +1 -0
  121. package/src/lib/form/dto/form-definition.input.d.ts +26 -0
  122. package/src/lib/form/dto/form-definition.input.js +126 -0
  123. package/src/lib/form/dto/form-definition.input.js.map +1 -0
  124. package/src/lib/form/form-definition-version.entity.d.ts +17 -0
  125. package/src/lib/form/form-definition-version.entity.js +84 -0
  126. package/src/lib/form/form-definition-version.entity.js.map +1 -0
  127. package/src/lib/form/form-definition.entity.d.ts +13 -0
  128. package/src/lib/form/form-definition.entity.js +66 -0
  129. package/src/lib/form/form-definition.entity.js.map +1 -0
  130. package/src/lib/form/form-schema-lint.object.d.ts +4 -0
  131. package/src/lib/form/form-schema-lint.object.js +20 -0
  132. package/src/lib/form/form-schema-lint.object.js.map +1 -0
  133. package/src/lib/form/form-schema.validator.d.ts +13 -0
  134. package/src/lib/form/form-schema.validator.js +354 -0
  135. package/src/lib/form/form-schema.validator.js.map +1 -0
  136. package/src/lib/form/form.enums.d.ts +9 -0
  137. package/src/lib/form/form.enums.js +22 -0
  138. package/src/lib/form/form.enums.js.map +1 -0
  139. package/src/lib/form/form.module.d.ts +2 -0
  140. package/src/lib/form/form.module.js +27 -0
  141. package/src/lib/form/form.module.js.map +1 -0
  142. package/src/lib/form/form.mutations.d.ts +14 -0
  143. package/src/lib/form/form.mutations.js +89 -0
  144. package/src/lib/form/form.mutations.js.map +1 -0
  145. package/src/lib/form/form.queries.d.ts +16 -0
  146. package/src/lib/form/form.queries.js +96 -0
  147. package/src/lib/form/form.queries.js.map +1 -0
  148. package/src/lib/form/form.service.d.ts +49 -0
  149. package/src/lib/form/form.service.js +346 -0
  150. package/src/lib/form/form.service.js.map +1 -0
  151. package/src/lib/form/index.js +12 -0
  152. package/src/lib/form/index.js.map +1 -0
  153. package/src/lib/identity/identity-options.d.ts +13 -0
  154. package/src/lib/identity/identity-options.js +20 -0
  155. package/src/lib/identity/identity-options.js.map +1 -0
  156. package/src/lib/identity/identity.module.d.ts +17 -0
  157. package/src/lib/identity/identity.module.js +65 -0
  158. package/src/lib/identity/identity.module.js.map +1 -0
  159. package/src/lib/identity/identity.queries.d.ts +12 -0
  160. package/src/lib/identity/identity.queries.js +79 -0
  161. package/src/lib/identity/identity.queries.js.map +1 -0
  162. package/src/lib/identity/identity.service.d.ts +31 -0
  163. package/src/lib/identity/identity.service.js +196 -0
  164. package/src/lib/identity/identity.service.js.map +1 -0
  165. package/src/lib/identity/index.js +8 -0
  166. package/src/lib/identity/index.js.map +1 -0
  167. package/src/lib/identity/member-base.adapter.d.ts +123 -0
  168. package/src/lib/identity/member-base.adapter.js +147 -0
  169. package/src/lib/identity/member-base.adapter.js.map +1 -0
  170. package/src/lib/identity/member-metadata-cache.entity.d.ts +8 -0
  171. package/src/lib/identity/member-metadata-cache.entity.js +39 -0
  172. package/src/lib/identity/member-metadata-cache.entity.js.map +1 -0
  173. package/src/lib/identity/{member-not-found.exception.ts → member-not-found.exception.d.ts} +3 -8
  174. package/src/lib/identity/member-not-found.exception.js +26 -0
  175. package/src/lib/identity/member-not-found.exception.js.map +1 -0
  176. package/src/lib/identity/member-profile.object.d.ts +8 -0
  177. package/src/lib/identity/member-profile.object.js +37 -0
  178. package/src/lib/identity/member-profile.object.js.map +1 -0
  179. package/src/lib/identity/{member-resolver.interface.ts → member-resolver.interface.d.ts} +41 -55
  180. package/src/lib/identity/member-resolver.interface.js +19 -0
  181. package/src/lib/identity/member-resolver.interface.js.map +1 -0
  182. package/src/lib/migrations/0000000000001-enable-postgres-extensions.d.ts +6 -0
  183. package/src/lib/migrations/0000000000001-enable-postgres-extensions.js +17 -0
  184. package/src/lib/migrations/0000000000001-enable-postgres-extensions.js.map +1 -0
  185. package/src/lib/migrations/0000000001000-identity-organization-foundation.d.ts +6 -0
  186. package/src/lib/migrations/{0000000001000-identity-organization-foundation.ts → 0000000001000-identity-organization-foundation.js} +29 -38
  187. package/src/lib/migrations/0000000001000-identity-organization-foundation.js.map +1 -0
  188. package/src/lib/migrations/0000000002000-form-builder-foundation.d.ts +6 -0
  189. package/src/lib/migrations/{0000000002000-form-builder-foundation.ts → 0000000002000-form-builder-foundation.js} +21 -22
  190. package/src/lib/migrations/0000000002000-form-builder-foundation.js.map +1 -0
  191. package/src/lib/migrations/0000000003000-approval-template-foundation.d.ts +6 -0
  192. package/src/lib/migrations/{0000000003000-approval-template-foundation.ts → 0000000003000-approval-template-foundation.js} +21 -24
  193. package/src/lib/migrations/0000000003000-approval-template-foundation.js.map +1 -0
  194. package/src/lib/migrations/0000000004000-workflow-engine-foundation.d.ts +6 -0
  195. package/src/lib/migrations/{0000000004000-workflow-engine-foundation.ts → 0000000004000-workflow-engine-foundation.js} +34 -46
  196. package/src/lib/migrations/0000000004000-workflow-engine-foundation.js.map +1 -0
  197. package/src/lib/migrations/0000000005000-delegation-rules.d.ts +6 -0
  198. package/src/lib/migrations/{0000000005000-delegation-rules.ts → 0000000005000-delegation-rules.js} +19 -20
  199. package/src/lib/migrations/0000000005000-delegation-rules.js.map +1 -0
  200. package/src/lib/migrations/0000000006000-notifications-sla.d.ts +6 -0
  201. package/src/lib/migrations/{0000000006000-notifications-sla.ts → 0000000006000-notifications-sla.js} +23 -26
  202. package/src/lib/migrations/0000000006000-notifications-sla.js.map +1 -0
  203. package/src/lib/migrations/0000000007000-signatures-attachments.d.ts +6 -0
  204. package/src/lib/migrations/{0000000007000-signatures-attachments.ts → 0000000007000-signatures-attachments.js} +27 -28
  205. package/src/lib/migrations/0000000007000-signatures-attachments.js.map +1 -0
  206. package/src/lib/migrations/0000000008000-approval-template-categories.d.ts +6 -0
  207. package/src/lib/migrations/{0000000008000-approval-template-categories.ts → 0000000008000-approval-template-categories.js} +25 -28
  208. package/src/lib/migrations/0000000008000-approval-template-categories.js.map +1 -0
  209. package/src/lib/migrations/0000000009000-task-candidates.d.ts +6 -0
  210. package/src/lib/migrations/{0000000009000-task-candidates.ts → 0000000009000-task-candidates.js} +23 -26
  211. package/src/lib/migrations/0000000009000-task-candidates.js.map +1 -0
  212. package/src/lib/migrations/0000000010000-notification-delivery-state.d.ts +6 -0
  213. package/src/lib/migrations/{0000000010000-notification-delivery-state.ts → 0000000010000-notification-delivery-state.js} +17 -16
  214. package/src/lib/migrations/0000000010000-notification-delivery-state.js.map +1 -0
  215. package/src/lib/migrations/0000000011000-remove-attachment-encryption-key.d.ts +6 -0
  216. package/src/lib/migrations/0000000011000-remove-attachment-encryption-key.js +22 -0
  217. package/src/lib/migrations/0000000011000-remove-attachment-encryption-key.js.map +1 -0
  218. package/src/lib/migrations/0000000012000-notification-sla-idempotency.d.ts +6 -0
  219. package/src/lib/migrations/0000000012000-notification-sla-idempotency.js +21 -0
  220. package/src/lib/migrations/0000000012000-notification-sla-idempotency.js.map +1 -0
  221. package/src/lib/migrations/0000000013000-workflow-query-indexes.d.ts +6 -0
  222. package/src/lib/migrations/0000000013000-workflow-query-indexes.js +80 -0
  223. package/src/lib/migrations/0000000013000-workflow-query-indexes.js.map +1 -0
  224. package/src/lib/migrations/0000000014000-notification-resolution.d.ts +6 -0
  225. package/src/lib/migrations/0000000014000-notification-resolution.js +30 -0
  226. package/src/lib/migrations/0000000014000-notification-resolution.js.map +1 -0
  227. package/src/lib/migrations/0000000015000-backfill-stale-notification-resolution.d.ts +15 -0
  228. package/src/lib/migrations/{0000000015000-backfill-stale-notification-resolution.ts → 0000000015000-backfill-stale-notification-resolution.js} +16 -15
  229. package/src/lib/migrations/0000000015000-backfill-stale-notification-resolution.js.map +1 -0
  230. package/src/lib/migrations/0000000016000-archive-parallel-form-drafts.d.ts +15 -0
  231. package/src/lib/migrations/{0000000016000-archive-parallel-form-drafts.ts → 0000000016000-archive-parallel-form-drafts.js} +16 -15
  232. package/src/lib/migrations/0000000016000-archive-parallel-form-drafts.js.map +1 -0
  233. package/src/lib/migrations/0000000017000-adhoc-directives.d.ts +6 -0
  234. package/src/lib/migrations/{0000000017000-adhoc-directives.ts → 0000000017000-adhoc-directives.js} +19 -18
  235. package/src/lib/migrations/0000000017000-adhoc-directives.js.map +1 -0
  236. package/src/lib/migrations/index.d.ts +20 -0
  237. package/src/lib/migrations/index.js +61 -0
  238. package/src/lib/migrations/index.js.map +1 -0
  239. package/src/lib/notification/dto/notification-preference.input.d.ts +9 -0
  240. package/src/lib/notification/dto/notification-preference.input.js +53 -0
  241. package/src/lib/notification/dto/notification-preference.input.js.map +1 -0
  242. package/src/lib/notification/index.js +11 -0
  243. package/src/lib/notification/index.js.map +1 -0
  244. package/src/lib/notification/notification-delivery-scheduler.service.d.ts +13 -0
  245. package/src/lib/notification/notification-delivery-scheduler.service.js +53 -0
  246. package/src/lib/notification/notification-delivery-scheduler.service.js.map +1 -0
  247. package/src/lib/notification/notification-delivery.service.d.ts +23 -0
  248. package/src/lib/notification/notification-delivery.service.js +245 -0
  249. package/src/lib/notification/notification-delivery.service.js.map +1 -0
  250. package/src/lib/notification/notification-dispatcher.token.d.ts +7 -0
  251. package/src/lib/notification/notification-dispatcher.token.js +5 -0
  252. package/src/lib/notification/notification-dispatcher.token.js.map +1 -0
  253. package/src/lib/notification/notification-options.d.ts +215 -0
  254. package/src/lib/notification/notification-options.js +128 -0
  255. package/src/lib/notification/notification-options.js.map +1 -0
  256. package/src/lib/notification/notification-options.module.d.ts +23 -0
  257. package/src/lib/notification/notification-options.module.js +41 -0
  258. package/src/lib/notification/notification-options.module.js.map +1 -0
  259. package/src/lib/notification/notification-preference.entity.d.ts +10 -0
  260. package/src/lib/notification/notification-preference.entity.js +50 -0
  261. package/src/lib/notification/notification-preference.entity.js.map +1 -0
  262. package/src/lib/notification/notification-sla-scheduler.service.d.ts +13 -0
  263. package/src/lib/notification/notification-sla-scheduler.service.js +51 -0
  264. package/src/lib/notification/notification-sla-scheduler.service.js.map +1 -0
  265. package/src/lib/notification/notification-template.d.ts +13 -0
  266. package/src/lib/notification/notification-template.js +61 -0
  267. package/src/lib/notification/notification-template.js.map +1 -0
  268. package/src/lib/notification/notification.entity.d.ts +31 -0
  269. package/src/lib/notification/notification.entity.js +149 -0
  270. package/src/lib/notification/notification.entity.js.map +1 -0
  271. package/src/lib/notification/notification.enums.d.ts +38 -0
  272. package/src/lib/notification/notification.enums.js +63 -0
  273. package/src/lib/notification/notification.enums.js.map +1 -0
  274. package/src/lib/notification/notification.module.d.ts +2 -0
  275. package/src/lib/notification/notification.module.js +45 -0
  276. package/src/lib/notification/notification.module.js.map +1 -0
  277. package/src/lib/notification/notification.mutations.d.ts +11 -0
  278. package/src/lib/notification/notification.mutations.js +64 -0
  279. package/src/lib/notification/notification.mutations.js.map +1 -0
  280. package/src/lib/notification/notification.queries.d.ts +11 -0
  281. package/src/lib/notification/notification.queries.js +77 -0
  282. package/src/lib/notification/notification.queries.js.map +1 -0
  283. package/src/lib/notification/notification.service.d.ts +121 -0
  284. package/src/lib/notification/notification.service.js +713 -0
  285. package/src/lib/notification/notification.service.js.map +1 -0
  286. package/src/lib/organization/dto/manager-resolution.input.d.ts +18 -0
  287. package/src/lib/organization/dto/manager-resolution.input.js +107 -0
  288. package/src/lib/organization/dto/manager-resolution.input.js.map +1 -0
  289. package/src/lib/organization/dto/membership.input.d.ts +16 -0
  290. package/src/lib/organization/dto/membership.input.js +99 -0
  291. package/src/lib/organization/dto/membership.input.js.map +1 -0
  292. package/src/lib/organization/dto/org-unit.input.d.ts +24 -0
  293. package/src/lib/organization/dto/org-unit.input.js +119 -0
  294. package/src/lib/organization/dto/org-unit.input.js.map +1 -0
  295. package/src/lib/organization/dto/position.input.d.ts +13 -0
  296. package/src/lib/organization/dto/position.input.js +70 -0
  297. package/src/lib/organization/dto/position.input.js.map +1 -0
  298. package/src/lib/organization/index.js +16 -0
  299. package/src/lib/organization/index.js.map +1 -0
  300. package/src/lib/organization/json-metadata.d.ts +1 -0
  301. package/src/lib/organization/json-metadata.js +14 -0
  302. package/src/lib/organization/json-metadata.js.map +1 -0
  303. package/src/lib/organization/manager-resolution.entity.d.ts +11 -0
  304. package/src/lib/organization/manager-resolution.entity.js +55 -0
  305. package/src/lib/organization/manager-resolution.entity.js.map +1 -0
  306. package/src/lib/organization/membership.entity.d.ts +11 -0
  307. package/src/lib/organization/membership.entity.js +60 -0
  308. package/src/lib/organization/membership.entity.js.map +1 -0
  309. package/src/lib/organization/org-unit-tree-commit-result.object.d.ts +4 -0
  310. package/src/lib/organization/org-unit-tree-commit-result.object.js +17 -0
  311. package/src/lib/organization/org-unit-tree-commit-result.object.js.map +1 -0
  312. package/src/lib/organization/org-unit.entity.d.ts +13 -0
  313. package/src/lib/organization/org-unit.entity.js +64 -0
  314. package/src/lib/organization/org-unit.entity.js.map +1 -0
  315. package/src/lib/organization/organization-summary.object.d.ts +6 -0
  316. package/src/lib/organization/organization-summary.object.js +28 -0
  317. package/src/lib/organization/organization-summary.object.js.map +1 -0
  318. package/src/lib/organization/organization.enums.d.ts +11 -0
  319. package/src/lib/organization/organization.enums.js +22 -0
  320. package/src/lib/organization/organization.enums.js.map +1 -0
  321. package/src/lib/organization/organization.module.d.ts +2 -0
  322. package/src/lib/organization/organization.module.js +35 -0
  323. package/src/lib/organization/organization.module.js.map +1 -0
  324. package/src/lib/organization/organization.mutations.d.ts +26 -0
  325. package/src/lib/organization/organization.mutations.js +148 -0
  326. package/src/lib/organization/organization.mutations.js.map +1 -0
  327. package/src/lib/organization/organization.queries.d.ts +23 -0
  328. package/src/lib/organization/organization.queries.js +204 -0
  329. package/src/lib/organization/organization.queries.js.map +1 -0
  330. package/src/lib/organization/organization.service.d.ts +103 -0
  331. package/src/lib/organization/organization.service.js +768 -0
  332. package/src/lib/organization/organization.service.js.map +1 -0
  333. package/src/lib/organization/position.entity.d.ts +9 -0
  334. package/src/lib/organization/position.entity.js +48 -0
  335. package/src/lib/organization/position.entity.js.map +1 -0
  336. package/src/lib/organization/resolved-manager.object.d.ts +4 -0
  337. package/src/lib/organization/resolved-manager.object.js +20 -0
  338. package/src/lib/organization/resolved-manager.object.js.map +1 -0
  339. package/src/lib/signature/index.js +8 -0
  340. package/src/lib/signature/index.js.map +1 -0
  341. package/src/lib/signature/signature-options.d.ts +33 -0
  342. package/src/lib/signature/signature-options.js +51 -0
  343. package/src/lib/signature/signature-options.js.map +1 -0
  344. package/src/lib/signature/signature-verification.object.d.ts +6 -0
  345. package/src/lib/signature/signature-verification.object.js +28 -0
  346. package/src/lib/signature/signature-verification.object.js.map +1 -0
  347. package/src/lib/signature/signature.entity.d.ts +16 -0
  348. package/src/lib/signature/signature.entity.js +88 -0
  349. package/src/lib/signature/signature.entity.js.map +1 -0
  350. package/src/lib/signature/signature.module.d.ts +11 -0
  351. package/src/lib/signature/signature.module.js +60 -0
  352. package/src/lib/signature/signature.module.js.map +1 -0
  353. package/src/lib/signature/signature.queries.d.ts +11 -0
  354. package/src/lib/signature/signature.queries.js +46 -0
  355. package/src/lib/signature/signature.queries.js.map +1 -0
  356. package/src/lib/signature/signature.service.d.ts +25 -0
  357. package/src/lib/signature/signature.service.js +124 -0
  358. package/src/lib/signature/signature.service.js.map +1 -0
  359. package/src/lib/template/approval-template-category.entity.d.ts +9 -0
  360. package/src/lib/template/approval-template-category.entity.js +49 -0
  361. package/src/lib/template/approval-template-category.entity.js.map +1 -0
  362. package/src/lib/template/approval-template-version.entity.d.ts +21 -0
  363. package/src/lib/template/approval-template-version.entity.js +106 -0
  364. package/src/lib/template/approval-template-version.entity.js.map +1 -0
  365. package/src/lib/template/approval-template.entity.d.ts +14 -0
  366. package/src/lib/template/approval-template.entity.js +74 -0
  367. package/src/lib/template/approval-template.entity.js.map +1 -0
  368. package/src/lib/template/compose-approval-template.object.d.ts +11 -0
  369. package/src/lib/template/compose-approval-template.object.js +36 -0
  370. package/src/lib/template/compose-approval-template.object.js.map +1 -0
  371. package/src/lib/template/compose-template.mutations.d.ts +8 -0
  372. package/src/lib/template/compose-template.mutations.js +32 -0
  373. package/src/lib/template/compose-template.mutations.js.map +1 -0
  374. package/src/lib/template/dto/approval-template.input.d.ts +36 -0
  375. package/src/lib/template/dto/approval-template.input.js +187 -0
  376. package/src/lib/template/dto/approval-template.input.js.map +1 -0
  377. package/src/lib/template/dto/compose-approval-template.input.d.ts +17 -0
  378. package/src/lib/template/dto/compose-approval-template.input.js +99 -0
  379. package/src/lib/template/dto/compose-approval-template.input.js.map +1 -0
  380. package/src/lib/template/index.js +14 -0
  381. package/src/lib/template/index.js.map +1 -0
  382. package/src/lib/template/template.enums.d.ts +14 -0
  383. package/src/lib/template/template.enums.js +31 -0
  384. package/src/lib/template/template.enums.js.map +1 -0
  385. package/src/lib/template/template.module.d.ts +2 -0
  386. package/src/lib/template/template.module.js +41 -0
  387. package/src/lib/template/template.module.js.map +1 -0
  388. package/src/lib/template/template.mutations.d.ts +20 -0
  389. package/src/lib/template/template.mutations.js +139 -0
  390. package/src/lib/template/template.mutations.js.map +1 -0
  391. package/src/lib/template/template.queries.d.ts +16 -0
  392. package/src/lib/template/template.queries.js +133 -0
  393. package/src/lib/template/template.queries.js.map +1 -0
  394. package/src/lib/template/template.service.d.ts +83 -0
  395. package/src/lib/template/template.service.js +654 -0
  396. package/src/lib/template/template.service.js.map +1 -0
  397. package/src/lib/template/workflow-definition.validator.d.ts +9 -0
  398. package/src/lib/template/workflow-definition.validator.js +362 -0
  399. package/src/lib/template/workflow-definition.validator.js.map +1 -0
  400. package/src/lib/testing/cel-js.jest.d.ts +15 -0
  401. package/src/lib/testing/cel-js.jest.js +27 -0
  402. package/src/lib/testing/cel-js.jest.js.map +1 -0
  403. package/src/lib/workflow-engine/activity-log.entity.d.ts +12 -0
  404. package/src/lib/workflow-engine/activity-log.entity.js +62 -0
  405. package/src/lib/workflow-engine/activity-log.entity.js.map +1 -0
  406. package/src/lib/workflow-engine/adhoc-directive.entity.d.ts +31 -0
  407. package/src/lib/workflow-engine/adhoc-directive.entity.js +105 -0
  408. package/src/lib/workflow-engine/adhoc-directive.entity.js.map +1 -0
  409. package/src/lib/workflow-engine/adhoc.enums.d.ts +21 -0
  410. package/src/lib/workflow-engine/adhoc.enums.js +42 -0
  411. package/src/lib/workflow-engine/adhoc.enums.js.map +1 -0
  412. package/src/lib/workflow-engine/approval-instance-page-info.object.d.ts +8 -0
  413. package/src/lib/workflow-engine/approval-instance-page-info.object.js +36 -0
  414. package/src/lib/workflow-engine/approval-instance-page-info.object.js.map +1 -0
  415. package/src/lib/workflow-engine/approval-instance.entity.d.ts +22 -0
  416. package/src/lib/workflow-engine/approval-instance.entity.js +113 -0
  417. package/src/lib/workflow-engine/approval-instance.entity.js.map +1 -0
  418. package/src/lib/workflow-engine/dto/adhoc-notification.input.d.ts +6 -0
  419. package/src/lib/workflow-engine/dto/adhoc-notification.input.js +28 -0
  420. package/src/lib/workflow-engine/dto/adhoc-notification.input.js.map +1 -0
  421. package/src/lib/workflow-engine/dto/adhoc-target.input.d.ts +16 -0
  422. package/src/lib/workflow-engine/dto/adhoc-target.input.js +61 -0
  423. package/src/lib/workflow-engine/dto/adhoc-target.input.js.map +1 -0
  424. package/src/lib/workflow-engine/dto/cancel-approval-instance.input.d.ts +5 -0
  425. package/src/lib/workflow-engine/dto/cancel-approval-instance.input.js +29 -0
  426. package/src/lib/workflow-engine/dto/cancel-approval-instance.input.js.map +1 -0
  427. package/src/lib/workflow-engine/dto/decide-task.input.d.ts +9 -0
  428. package/src/lib/workflow-engine/dto/decide-task.input.js +47 -0
  429. package/src/lib/workflow-engine/dto/decide-task.input.js.map +1 -0
  430. package/src/lib/workflow-engine/dto/dry-run-approval-workflow.input.d.ts +6 -0
  431. package/src/lib/workflow-engine/dto/dry-run-approval-workflow.input.js +34 -0
  432. package/src/lib/workflow-engine/dto/dry-run-approval-workflow.input.js.map +1 -0
  433. package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.d.ts +6 -0
  434. package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.js +34 -0
  435. package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.js.map +1 -0
  436. package/src/lib/workflow-engine/dto/submit-approval-instance.input.d.ts +7 -0
  437. package/src/lib/workflow-engine/dto/submit-approval-instance.input.js +40 -0
  438. package/src/lib/workflow-engine/dto/submit-approval-instance.input.js.map +1 -0
  439. package/src/lib/workflow-engine/index.js +28 -0
  440. package/src/lib/workflow-engine/index.js.map +1 -0
  441. package/src/lib/workflow-engine/task-candidate.entity.d.ts +14 -0
  442. package/src/lib/workflow-engine/task-candidate.entity.js +72 -0
  443. package/src/lib/workflow-engine/task-candidate.entity.js.map +1 -0
  444. package/src/lib/workflow-engine/task-decision.entity.d.ts +12 -0
  445. package/src/lib/workflow-engine/task-decision.entity.js +60 -0
  446. package/src/lib/workflow-engine/task-decision.entity.js.map +1 -0
  447. package/src/lib/workflow-engine/task.entity.d.ts +25 -0
  448. package/src/lib/workflow-engine/task.entity.js +130 -0
  449. package/src/lib/workflow-engine/task.entity.js.map +1 -0
  450. package/src/lib/workflow-engine/workflow-condition-evaluator.d.ts +5 -0
  451. package/src/lib/workflow-engine/workflow-condition-evaluator.js +107 -0
  452. package/src/lib/workflow-engine/workflow-condition-evaluator.js.map +1 -0
  453. package/src/lib/workflow-engine/workflow-dashboard-summary.object.d.ts +8 -0
  454. package/src/lib/workflow-engine/workflow-dashboard-summary.object.js +36 -0
  455. package/src/lib/workflow-engine/workflow-dashboard-summary.object.js.map +1 -0
  456. package/src/lib/workflow-engine/workflow-dry-run.object.d.ts +21 -0
  457. package/src/lib/workflow-engine/workflow-dry-run.object.js +86 -0
  458. package/src/lib/workflow-engine/workflow-dry-run.object.js.map +1 -0
  459. package/src/lib/workflow-engine/workflow-engine.enums.d.ts +61 -0
  460. package/src/lib/workflow-engine/workflow-engine.enums.js +98 -0
  461. package/src/lib/workflow-engine/workflow-engine.enums.js.map +1 -0
  462. package/src/lib/workflow-engine/workflow-engine.module.d.ts +9 -0
  463. package/src/lib/workflow-engine/workflow-engine.module.js +78 -0
  464. package/src/lib/workflow-engine/workflow-engine.module.js.map +1 -0
  465. package/src/lib/workflow-engine/workflow-engine.mutations.d.ts +29 -0
  466. package/src/lib/workflow-engine/workflow-engine.mutations.js +218 -0
  467. package/src/lib/workflow-engine/workflow-engine.mutations.js.map +1 -0
  468. package/src/lib/workflow-engine/workflow-engine.queries.d.ts +31 -0
  469. package/src/lib/workflow-engine/workflow-engine.queries.js +237 -0
  470. package/src/lib/workflow-engine/workflow-engine.queries.js.map +1 -0
  471. package/src/lib/workflow-engine/workflow-engine.service.d.ts +188 -0
  472. package/src/lib/workflow-engine/workflow-engine.service.js +3561 -0
  473. package/src/lib/workflow-engine/workflow-engine.service.js.map +1 -0
  474. package/src/lib/workflow-engine/{workflow-engine.tokens.ts → workflow-engine.tokens.d.ts} +3 -9
  475. package/src/lib/workflow-engine/workflow-engine.tokens.js +5 -0
  476. package/src/lib/workflow-engine/workflow-engine.tokens.js.map +1 -0
  477. package/src/lib/workflow-engine/workflow-service-task-dispatcher.token.d.ts +18 -0
  478. package/src/lib/workflow-engine/workflow-service-task-dispatcher.token.js +39 -0
  479. package/src/lib/workflow-engine/workflow-service-task-dispatcher.token.js.map +1 -0
  480. package/src/lib/workflow-engine/workflow-token.entity.d.ts +10 -0
  481. package/src/lib/workflow-engine/workflow-token.entity.js +50 -0
  482. package/src/lib/workflow-engine/workflow-token.entity.js.map +1 -0
  483. package/eslint.config.mjs +0 -20
  484. package/jest.config.cts +0 -15
  485. package/project.json +0 -58
  486. package/src/lib/attachment/attachment-options.spec.ts +0 -45
  487. package/src/lib/attachment/attachment-options.ts +0 -130
  488. package/src/lib/attachment/attachment-storage.provider.spec.ts +0 -79
  489. package/src/lib/attachment/attachment-storage.provider.ts +0 -60
  490. package/src/lib/attachment/attachment.controller.ts +0 -39
  491. package/src/lib/attachment/attachment.entity.ts +0 -59
  492. package/src/lib/attachment/attachment.module.ts +0 -149
  493. package/src/lib/attachment/attachment.mutations.ts +0 -22
  494. package/src/lib/attachment/attachment.queries.ts +0 -65
  495. package/src/lib/attachment/attachment.service.spec.ts +0 -319
  496. package/src/lib/attachment/attachment.service.ts +0 -477
  497. package/src/lib/attachment/dto/upload-attachment.input.ts +0 -60
  498. package/src/lib/bpm/bpm-root.module.spec.ts +0 -138
  499. package/src/lib/bpm/bpm-root.module.ts +0 -320
  500. package/src/lib/bpm-auth/bpm-auth-context.extractor.ts +0 -106
  501. package/src/lib/bpm-auth/bpm-auth-context.ts +0 -40
  502. package/src/lib/bpm-auth/bpm-auth.authorization.ts +0 -80
  503. package/src/lib/bpm-auth/bpm-auth.decorators.ts +0 -19
  504. package/src/lib/bpm-auth/bpm-auth.guard.ts +0 -31
  505. package/src/lib/bpm-auth/bpm-auth.module.spec.ts +0 -106
  506. package/src/lib/bpm-auth/bpm-auth.module.ts +0 -69
  507. package/src/lib/bpm-auth/bpm-auth.options.ts +0 -21
  508. package/src/lib/bpm-auth/configurable-bpm-auth-context.accessor.ts +0 -55
  509. package/src/lib/common/filters/all-exceptions.filter.ts +0 -138
  510. package/src/lib/common/iso-duration.ts +0 -31
  511. package/src/lib/condition/condition.module.ts +0 -8
  512. package/src/lib/condition/condition.service.spec.ts +0 -50
  513. package/src/lib/condition/condition.service.ts +0 -141
  514. package/src/lib/database/data-source.ts +0 -7
  515. package/src/lib/database/migration-runner.ts +0 -26
  516. package/src/lib/database/reconcile-legacy-migrations.ts +0 -87
  517. package/src/lib/database/typeorm.config.ts +0 -217
  518. package/src/lib/delegation/delegation-rule.entity.ts +0 -84
  519. package/src/lib/delegation/delegation.enums.ts +0 -21
  520. package/src/lib/delegation/delegation.module.ts +0 -14
  521. package/src/lib/delegation/delegation.mutations.ts +0 -92
  522. package/src/lib/delegation/delegation.queries.ts +0 -111
  523. package/src/lib/delegation/delegation.service.spec.ts +0 -295
  524. package/src/lib/delegation/delegation.service.ts +0 -482
  525. package/src/lib/delegation/dto/delegation-rule.input.ts +0 -125
  526. package/src/lib/form/dto/form-definition.input.ts +0 -90
  527. package/src/lib/form/form-definition-version.entity.ts +0 -66
  528. package/src/lib/form/form-definition.entity.ts +0 -54
  529. package/src/lib/form/form-schema-lint.object.ts +0 -10
  530. package/src/lib/form/form-schema.validator.spec.ts +0 -180
  531. package/src/lib/form/form-schema.validator.ts +0 -538
  532. package/src/lib/form/form.enums.ts +0 -20
  533. package/src/lib/form/form.module.ts +0 -19
  534. package/src/lib/form/form.mutations.ts +0 -73
  535. package/src/lib/form/form.queries.ts +0 -72
  536. package/src/lib/form/form.service.spec.ts +0 -371
  537. package/src/lib/form/form.service.ts +0 -607
  538. package/src/lib/identity/identity-options.ts +0 -41
  539. package/src/lib/identity/identity.module.ts +0 -96
  540. package/src/lib/identity/identity.queries.ts +0 -61
  541. package/src/lib/identity/identity.service.spec.ts +0 -328
  542. package/src/lib/identity/identity.service.ts +0 -277
  543. package/src/lib/identity/member-base.adapter.spec.ts +0 -122
  544. package/src/lib/identity/member-base.adapter.ts +0 -302
  545. package/src/lib/identity/member-metadata-cache.entity.ts +0 -27
  546. package/src/lib/identity/member-profile.object.ts +0 -28
  547. package/src/lib/migrations/0000000000001-enable-postgres-extensions.ts +0 -14
  548. package/src/lib/migrations/0000000011000-remove-attachment-encryption-key.ts +0 -19
  549. package/src/lib/migrations/0000000012000-notification-sla-idempotency.ts +0 -22
  550. package/src/lib/migrations/0000000013000-workflow-query-indexes.ts +0 -113
  551. package/src/lib/migrations/0000000014000-notification-resolution.ts +0 -29
  552. package/src/lib/migrations/index.ts +0 -59
  553. package/src/lib/notification/dto/notification-preference.input.ts +0 -46
  554. package/src/lib/notification/notification-delivery-scheduler.service.ts +0 -69
  555. package/src/lib/notification/notification-delivery.service.spec.ts +0 -315
  556. package/src/lib/notification/notification-delivery.service.ts +0 -339
  557. package/src/lib/notification/notification-dispatcher.token.ts +0 -13
  558. package/src/lib/notification/notification-options.module.ts +0 -67
  559. package/src/lib/notification/notification-options.spec.ts +0 -93
  560. package/src/lib/notification/notification-options.ts +0 -452
  561. package/src/lib/notification/notification-preference.entity.ts +0 -35
  562. package/src/lib/notification/notification-sla-scheduler.service.ts +0 -66
  563. package/src/lib/notification/notification-template.spec.ts +0 -33
  564. package/src/lib/notification/notification-template.ts +0 -97
  565. package/src/lib/notification/notification.entity.ts +0 -129
  566. package/src/lib/notification/notification.enums.ts +0 -64
  567. package/src/lib/notification/notification.module.ts +0 -37
  568. package/src/lib/notification/notification.mutations.ts +0 -47
  569. package/src/lib/notification/notification.queries.ts +0 -64
  570. package/src/lib/notification/notification.service.spec.ts +0 -412
  571. package/src/lib/notification/notification.service.ts +0 -1129
  572. package/src/lib/organization/dto/manager-resolution.input.ts +0 -91
  573. package/src/lib/organization/dto/membership.input.ts +0 -77
  574. package/src/lib/organization/dto/org-unit.input.ts +0 -96
  575. package/src/lib/organization/dto/position.input.ts +0 -50
  576. package/src/lib/organization/json-metadata.ts +0 -15
  577. package/src/lib/organization/manager-resolution.entity.ts +0 -44
  578. package/src/lib/organization/membership.entity.ts +0 -50
  579. package/src/lib/organization/org-unit-tree-commit-result.object.ts +0 -8
  580. package/src/lib/organization/org-unit.entity.ts +0 -53
  581. package/src/lib/organization/organization-summary.object.ts +0 -16
  582. package/src/lib/organization/organization.enums.ts +0 -19
  583. package/src/lib/organization/organization.module.ts +0 -27
  584. package/src/lib/organization/organization.mutations.ts +0 -115
  585. package/src/lib/organization/organization.queries.ts +0 -191
  586. package/src/lib/organization/organization.service.spec.ts +0 -835
  587. package/src/lib/organization/organization.service.ts +0 -1245
  588. package/src/lib/organization/position.entity.ts +0 -39
  589. package/src/lib/organization/resolved-manager.object.ts +0 -10
  590. package/src/lib/signature/signature-options.spec.ts +0 -60
  591. package/src/lib/signature/signature-options.ts +0 -120
  592. package/src/lib/signature/signature-verification.object.ts +0 -16
  593. package/src/lib/signature/signature.entity.ts +0 -62
  594. package/src/lib/signature/signature.module.ts +0 -80
  595. package/src/lib/signature/signature.queries.ts +0 -37
  596. package/src/lib/signature/signature.service.spec.ts +0 -134
  597. package/src/lib/signature/signature.service.ts +0 -205
  598. package/src/lib/template/approval-template-category.entity.ts +0 -40
  599. package/src/lib/template/approval-template-version.entity.ts +0 -82
  600. package/src/lib/template/approval-template.entity.ts +0 -64
  601. package/src/lib/template/compose-approval-template.object.ts +0 -23
  602. package/src/lib/template/compose-template.mutations.ts +0 -22
  603. package/src/lib/template/dto/approval-template.input.ts +0 -147
  604. package/src/lib/template/dto/compose-approval-template.input.ts +0 -80
  605. package/src/lib/template/template.enums.ts +0 -30
  606. package/src/lib/template/template.module.ts +0 -33
  607. package/src/lib/template/template.mutations.ts +0 -106
  608. package/src/lib/template/template.queries.ts +0 -119
  609. package/src/lib/template/template.service.spec.ts +0 -822
  610. package/src/lib/template/template.service.ts +0 -1082
  611. package/src/lib/template/workflow-definition.validator.spec.ts +0 -595
  612. package/src/lib/template/workflow-definition.validator.ts +0 -539
  613. package/src/lib/testing/cel-js.jest.ts +0 -46
  614. package/src/lib/workflow-engine/activity-log.entity.ts +0 -48
  615. package/src/lib/workflow-engine/adhoc-directive.entity.ts +0 -90
  616. package/src/lib/workflow-engine/adhoc.enums.ts +0 -42
  617. package/src/lib/workflow-engine/approval-instance-page-info.object.ts +0 -22
  618. package/src/lib/workflow-engine/approval-instance.entity.ts +0 -86
  619. package/src/lib/workflow-engine/dto/adhoc-notification.input.ts +0 -18
  620. package/src/lib/workflow-engine/dto/adhoc-target.input.ts +0 -52
  621. package/src/lib/workflow-engine/dto/cancel-approval-instance.input.ts +0 -18
  622. package/src/lib/workflow-engine/dto/decide-task.input.ts +0 -33
  623. package/src/lib/workflow-engine/dto/dry-run-approval-workflow.input.ts +0 -22
  624. package/src/lib/workflow-engine/dto/resubmit-approval-instance.input.ts +0 -22
  625. package/src/lib/workflow-engine/dto/submit-approval-instance.input.ts +0 -27
  626. package/src/lib/workflow-engine/task-candidate.entity.ts +0 -56
  627. package/src/lib/workflow-engine/task-decision.entity.ts +0 -43
  628. package/src/lib/workflow-engine/task.entity.ts +0 -102
  629. package/src/lib/workflow-engine/workflow-condition-evaluator.ts +0 -168
  630. package/src/lib/workflow-engine/workflow-dashboard-summary.object.ts +0 -22
  631. package/src/lib/workflow-engine/workflow-dry-run.object.ts +0 -58
  632. package/src/lib/workflow-engine/workflow-engine.enums.ts +0 -102
  633. package/src/lib/workflow-engine/workflow-engine.module.ts +0 -77
  634. package/src/lib/workflow-engine/workflow-engine.mutations.ts +0 -193
  635. package/src/lib/workflow-engine/workflow-engine.queries.ts +0 -241
  636. package/src/lib/workflow-engine/workflow-engine.service.spec.ts +0 -3345
  637. package/src/lib/workflow-engine/workflow-engine.service.ts +0 -6022
  638. package/src/lib/workflow-engine/workflow-service-task-dispatcher.token.ts +0 -69
  639. package/src/lib/workflow-engine/workflow-token.entity.ts +0 -40
  640. package/tsconfig.json +0 -24
  641. package/tsconfig.lib.json +0 -16
  642. package/tsconfig.spec.json +0 -16
  643. /package/src/{index.ts → index.d.ts} +0 -0
  644. /package/src/lib/attachment/{index.ts → index.d.ts} +0 -0
  645. /package/src/lib/bpm/{index.ts → index.d.ts} +0 -0
  646. /package/src/lib/bpm-auth/{index.ts → index.d.ts} +0 -0
  647. /package/src/lib/common/{index.ts → index.d.ts} +0 -0
  648. /package/src/lib/condition/{index.ts → index.d.ts} +0 -0
  649. /package/src/lib/database/{index.ts → index.d.ts} +0 -0
  650. /package/src/lib/delegation/{index.ts → index.d.ts} +0 -0
  651. /package/src/lib/form/{index.ts → index.d.ts} +0 -0
  652. /package/src/lib/identity/{index.ts → index.d.ts} +0 -0
  653. /package/src/lib/notification/{index.ts → index.d.ts} +0 -0
  654. /package/src/lib/organization/{index.ts → index.d.ts} +0 -0
  655. /package/src/lib/signature/{index.ts → index.d.ts} +0 -0
  656. /package/src/lib/template/{index.ts → index.d.ts} +0 -0
  657. /package/src/lib/workflow-engine/{index.ts → index.d.ts} +0 -0
@@ -1,3345 +0,0 @@
1
- import { FormDefinitionSchema } from '@rytass/bpm-core-shared/form';
2
- import {
3
- ApproverResolver,
4
- WorkflowDefinition,
5
- } from '@rytass/bpm-core-shared/workflow';
6
- import { ObjectLiteral } from 'typeorm';
7
- import { BPMAuthContext } from '../bpm-auth';
8
- import { AttachmentService } from '../attachment/attachment.service';
9
- import { ConditionService } from '../condition/condition.service';
10
- import {
11
- DelegationResolution,
12
- DelegationService,
13
- } from '../delegation/delegation.service';
14
- import { FormDefinitionVersionEntity } from '../form/form-definition-version.entity';
15
- import { FormDefinitionVersionStatusEnum } from '../form/form.enums';
16
- import { NotificationEntity } from '../notification/notification.entity';
17
- import { NotificationResolutionEnum } from '../notification/notification.enums';
18
- import { NotificationService } from '../notification/notification.service';
19
- import { ManagerResolutionEntity } from '../organization/manager-resolution.entity';
20
- import { MembershipEntity } from '../organization/membership.entity';
21
- import { OrgUnitEntity } from '../organization/org-unit.entity';
22
- import {
23
- ManagerResolutionScopeTypeEnum,
24
- OrgUnitTypeEnum,
25
- } from '../organization/organization.enums';
26
- import { SignatureEntity } from '../signature/signature.entity';
27
- import { SignatureService } from '../signature/signature.service';
28
- import { ApprovalTemplateVersionEntity } from '../template/approval-template-version.entity';
29
- import { ApprovalTemplateEntity } from '../template/approval-template.entity';
30
- import { ApprovalTemplateVersionStatusEnum } from '../template/template.enums';
31
- import { ActivityLogEntity } from './activity-log.entity';
32
- import { AdhocDirectiveEntity } from './adhoc-directive.entity';
33
- import {
34
- AdhocDirectiveStatusEnum,
35
- AdhocDirectiveTypeEnum,
36
- AdhocPreApprovalRejectBehaviorEnum,
37
- AdhocTargetKindEnum,
38
- } from './adhoc.enums';
39
- import { ApprovalInstanceEntity } from './approval-instance.entity';
40
- import { TaskCandidateEntity } from './task-candidate.entity';
41
- import { TaskDecisionEntity } from './task-decision.entity';
42
- import { TaskEntity } from './task.entity';
43
- import {
44
- ActivityLogEventTypeEnum,
45
- ApprovalInstanceListViewEnum,
46
- ApprovalInstanceStateEnum,
47
- TaskAssignmentTypeEnum,
48
- TaskCandidateStatusEnum,
49
- TaskDecisionActionEnum,
50
- TaskStatusEnum,
51
- WorkflowTokenStatusEnum,
52
- } from './workflow-engine.enums';
53
- import { WorkflowEngineService } from './workflow-engine.service';
54
- import { BPMWorkflowServiceTaskDispatcher } from './workflow-service-task-dispatcher.token';
55
- import { WorkflowTokenEntity } from './workflow-token.entity';
56
-
57
- describe('WorkflowEngineService', () => {
58
- it('submits an approval instance with snapshots, start token, and activity logs', async (): Promise<void> => {
59
- const fixture = createServiceFixture({
60
- currentVersionId: 'template-version-1',
61
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
62
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
63
- });
64
-
65
- const instance = await fixture.service.submitApprovalInstance({
66
- formDataJson: '{"amount":1000}',
67
- initiatorMemberId: 'member-001',
68
- initiatorMetadataSnapshotJson: '{"memberId":"member-001","roles":["IT"]}',
69
- templateId: 'template-1',
70
- title: null,
71
- });
72
-
73
- expect(instance.state).toBe(ApprovalInstanceStateEnum.RUNNING);
74
- expect(instance.workflowSnapshot.nodes[0]?.id).toBe('start');
75
- expect(instance.formDefinitionSnapshot).toMatchObject({
76
- formDefinitionVersionId: 'form-version-1',
77
- version: 1,
78
- });
79
- expect(instance.formData).toEqual({ amount: 1000 });
80
- expect(fixture.savedToken).toMatchObject({
81
- currentNodeId: 'start',
82
- instanceId: 'instance-1',
83
- status: WorkflowTokenStatusEnum.ACTIVE,
84
- });
85
- expect(fixture.savedActivityLogs.map((log) => log.eventType)).toEqual([
86
- ActivityLogEventTypeEnum.INSTANCE_STARTED,
87
- ActivityLogEventTypeEnum.TOKEN_CREATED,
88
- ]);
89
- });
90
-
91
- it('builds initiator metadata from active memberships when submit omits a snapshot', async (): Promise<void> => {
92
- const fixture = createServiceFixture({
93
- currentVersionId: 'template-version-1',
94
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
95
- processMemberships: [
96
- createMembership({
97
- id: 'membership-sales',
98
- isPrimary: true,
99
- orgUnitId: 'org-sales',
100
- positionId: 'position-manager',
101
- }),
102
- createMembership({
103
- id: 'membership-ops',
104
- isPrimary: false,
105
- orgUnitId: 'org-ops',
106
- positionId: 'position-staff',
107
- }),
108
- createMembership({
109
- effectiveTo: '2026-01-31',
110
- id: 'membership-ended',
111
- orgUnitId: 'org-ended',
112
- positionId: 'position-ended',
113
- }),
114
- ],
115
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
116
- });
117
-
118
- await fixture.service.submitApprovalInstance({
119
- formDataJson: '{"amount":1000}',
120
- initiatorMemberId: 'member-001',
121
- initiatorMetadataSnapshotJson: null,
122
- templateId: 'template-1',
123
- title: null,
124
- });
125
-
126
- expect(fixture.savedInstance?.initiatorMetadataSnapshot).toMatchObject({
127
- memberId: 'member-001',
128
- orgUnitIds: ['org-sales', 'org-ops'],
129
- positionId: 'position-manager',
130
- positionIds: ['position-manager', 'position-staff'],
131
- primaryOrgUnitId: 'org-sales',
132
- });
133
- });
134
-
135
- it('rejects submit when the template has no published current version', async (): Promise<void> => {
136
- const fixture = createServiceFixture({
137
- currentVersionId: null,
138
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
139
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
140
- });
141
-
142
- await expect(
143
- fixture.service.submitApprovalInstance({
144
- formDataJson: '{}',
145
- initiatorMemberId: 'member-001',
146
- initiatorMetadataSnapshotJson: null,
147
- templateId: 'template-1',
148
- title: 'Request',
149
- }),
150
- ).rejects.toThrow('Approval template does not have a published version');
151
- });
152
-
153
- it('rejects submit when required form fields are missing', async (): Promise<void> => {
154
- const fixture = createServiceFixture({
155
- currentVersionId: 'template-version-1',
156
- formSchema: createRequiredReasonFormSchema(),
157
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
158
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
159
- });
160
-
161
- await expect(
162
- fixture.service.submitApprovalInstance({
163
- formDataJson: '{}',
164
- initiatorMemberId: 'member-001',
165
- initiatorMetadataSnapshotJson: null,
166
- templateId: 'template-1',
167
- title: 'Request',
168
- }),
169
- ).rejects.toThrow('Form data is missing required fields: 事由');
170
- });
171
-
172
- it('requires conditional fields only when their condition is visible and required', async (): Promise<void> => {
173
- const fixture = createServiceFixture({
174
- currentVersionId: 'template-version-1',
175
- formSchema: createConditionalAttachmentFormSchema(),
176
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
177
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
178
- });
179
-
180
- await expect(
181
- fixture.service.submitApprovalInstance({
182
- formDataJson: '{"needsAttachment":false}',
183
- initiatorMemberId: 'member-001',
184
- initiatorMetadataSnapshotJson: null,
185
- templateId: 'template-1',
186
- title: 'Request',
187
- }),
188
- ).resolves.toMatchObject({ id: 'instance-1' });
189
-
190
- await expect(
191
- fixture.service.submitApprovalInstance({
192
- formDataJson: '{"needsAttachment":true}',
193
- initiatorMemberId: 'member-001',
194
- initiatorMetadataSnapshotJson: null,
195
- templateId: 'template-1',
196
- title: 'Request',
197
- }),
198
- ).rejects.toThrow('Form data is missing required fields: 附件');
199
- });
200
-
201
- it('rejects resubmit when returned instance form data is missing required fields', async (): Promise<void> => {
202
- const formSchema = createRequiredReasonFormSchema();
203
- const fixture = createServiceFixture({
204
- currentVersionId: 'template-version-1',
205
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
206
- instanceState: ApprovalInstanceStateEnum.RETURNED,
207
- processFormDefinitionSnapshot: { schema: formSchema },
208
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
209
- });
210
-
211
- await expect(
212
- fixture.service.resubmitApprovalInstance({
213
- formDataJson: '{}',
214
- initiatorMemberId: 'member-001',
215
- instanceId: 'instance-1',
216
- title: 'Request',
217
- }),
218
- ).rejects.toThrow('Form data is missing required fields: 事由');
219
- });
220
-
221
- it('uses an advisory lock when processing an instance', async (): Promise<void> => {
222
- const fixture = createServiceFixture({
223
- currentVersionId: 'template-version-1',
224
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
225
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
226
- });
227
-
228
- await fixture.service.processInstance('instance-1');
229
-
230
- expect(fixture.managerQuery).toHaveBeenCalledWith(
231
- 'SELECT pg_advisory_xact_lock(hashtext($1))',
232
- ['instance-1'],
233
- );
234
- expect(fixture.savedProcessLog?.eventType).toBe(
235
- ActivityLogEventTypeEnum.ENGINE_PROCESS_REQUESTED,
236
- );
237
- });
238
-
239
- it('advances a start token and creates a direct user task', async (): Promise<void> => {
240
- const fixture = createServiceFixture({
241
- currentVersionId: 'template-version-1',
242
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
243
- processWorkflowSnapshot: createLinearUserTaskWorkflow(),
244
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
245
- });
246
-
247
- await fixture.service.processInstance('instance-1');
248
-
249
- expect(fixture.savedProcessToken).toMatchObject({
250
- currentNodeId: 'task_finance',
251
- status: WorkflowTokenStatusEnum.WAITING,
252
- });
253
- expect(fixture.savedTasks).toEqual([
254
- expect.objectContaining({
255
- assigneeMemberId: 'member-finance',
256
- instanceId: 'instance-1',
257
- nodeId: 'task_finance',
258
- originalAssigneeMemberId: 'member-finance',
259
- status: TaskStatusEnum.PENDING,
260
- tokenId: 'token-1',
261
- }),
262
- ]);
263
- expect(fixture.savedSingleActivityLogs.map((log) => log.eventType)).toEqual(
264
- [
265
- ActivityLogEventTypeEnum.ENGINE_PROCESS_REQUESTED,
266
- ActivityLogEventTypeEnum.TOKEN_ADVANCED,
267
- ActivityLogEventTypeEnum.TASK_CREATED,
268
- ],
269
- );
270
- expect(
271
- fixture.notificationService.createTaskAssignedNotification,
272
- ).toHaveBeenCalledWith(
273
- expect.objectContaining({
274
- instance: expect.objectContaining({ id: 'instance-1' }),
275
- node: expect.objectContaining({ id: 'task_finance' }),
276
- task: expect.objectContaining({ assigneeMemberId: 'member-finance' }),
277
- }),
278
- );
279
- });
280
-
281
- it('sets task SLA due time from user task SLA config', async (): Promise<void> => {
282
- const fixture = createServiceFixture({
283
- currentVersionId: 'template-version-1',
284
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
285
- processWorkflowSnapshot: createLinearUserTaskWorkflow({
286
- slaDuration: 'PT2H',
287
- }),
288
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
289
- });
290
-
291
- await fixture.service.processInstance('instance-1');
292
-
293
- expect(fixture.savedTasks[0]?.slaDueAt).toBeInstanceOf(Date);
294
- });
295
-
296
- it('creates notifications from notify service task recipients', async (): Promise<void> => {
297
- const fixture = createServiceFixture({
298
- currentVersionId: 'template-version-1',
299
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
300
- processWorkflowSnapshot: createNotifyServiceTaskWorkflow(),
301
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
302
- });
303
-
304
- await fixture.service.processInstance('instance-1');
305
-
306
- expect(
307
- fixture.notificationService.createServiceTaskNotifications,
308
- ).toHaveBeenCalledWith(
309
- expect.objectContaining({
310
- instance: expect.objectContaining({ id: 'instance-1' }),
311
- node: expect.objectContaining({ id: 'notify_finance' }),
312
- recipientMemberIds: ['member-finance', 'member-admin'],
313
- }),
314
- );
315
- expect(fixture.savedSingleActivityLogs.at(-1)).toMatchObject({
316
- eventType: ActivityLogEventTypeEnum.TOKEN_ADVANCED,
317
- payload: expect.objectContaining({
318
- action: 'NOTIFY',
319
- recipientMemberIds: ['member-finance', 'member-admin'],
320
- }),
321
- });
322
- });
323
-
324
- it('records webhook failures and continues processing', async (): Promise<void> => {
325
- const fetchSpy = jest
326
- .spyOn(globalThis, 'fetch')
327
- .mockResolvedValueOnce(new Response('remote failure', { status: 500 }));
328
- const fixture = createServiceFixture({
329
- currentVersionId: 'template-version-1',
330
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
331
- processWorkflowSnapshot: createWebhookServiceTaskWorkflow(),
332
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
333
- });
334
-
335
- await fixture.service.processInstance('instance-1');
336
-
337
- expect(fetchSpy).toHaveBeenCalledWith(
338
- 'https://example.test/workflow-hook',
339
- expect.objectContaining({
340
- body: expect.stringContaining('"title":"費用申請"'),
341
- method: 'POST',
342
- }),
343
- );
344
- expect(fixture.savedSingleActivityLogs).toContainEqual(
345
- expect.objectContaining({
346
- eventType: ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
347
- payload: expect.objectContaining({
348
- action: 'WEBHOOK',
349
- ok: false,
350
- status: 500,
351
- }),
352
- }),
353
- );
354
- expect(fixture.savedInstance).toMatchObject({
355
- completedAt: expect.any(Date),
356
- state: ApprovalInstanceStateEnum.APPROVED,
357
- });
358
-
359
- fetchSpy.mockRestore();
360
- });
361
-
362
- it('uses the injected workflow service task dispatcher for webhook nodes', async (): Promise<void> => {
363
- const serviceTaskDispatcher: BPMWorkflowServiceTaskDispatcher = {
364
- dispatchWebhook: jest.fn(() =>
365
- Promise.resolve({ ok: true, status: 202 }),
366
- ),
367
- };
368
- const fixture = createServiceFixture({
369
- currentVersionId: 'template-version-1',
370
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
371
- processFormData: { amount: 1000 },
372
- processWorkflowSnapshot: createWebhookServiceTaskWorkflow(),
373
- serviceTaskDispatcher,
374
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
375
- });
376
-
377
- await fixture.service.processInstance('instance-1');
378
-
379
- expect(serviceTaskDispatcher.dispatchWebhook).toHaveBeenCalledWith({
380
- headers: undefined,
381
- payload: { amount: 1000, title: '費用申請' },
382
- url: 'https://example.test/workflow-hook',
383
- });
384
- expect(fixture.savedSingleActivityLogs).toContainEqual(
385
- expect.objectContaining({
386
- eventType: ActivityLogEventTypeEnum.SERVICE_TASK_EXECUTED,
387
- payload: expect.objectContaining({
388
- action: 'WEBHOOK',
389
- ok: true,
390
- status: 202,
391
- }),
392
- }),
393
- );
394
- });
395
-
396
- it('updates form data from set-form-field service tasks', async (): Promise<void> => {
397
- const fixture = createServiceFixture({
398
- currentVersionId: 'template-version-1',
399
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
400
- processFormData: { amount: 1200 },
401
- processWorkflowSnapshot: createSetFormFieldServiceTaskWorkflow(),
402
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
403
- });
404
-
405
- await fixture.service.processInstance('instance-1');
406
-
407
- expect(fixture.savedSingleActivityLogs).toContainEqual(
408
- expect.objectContaining({
409
- eventType: ActivityLogEventTypeEnum.SERVICE_TASK_EXECUTED,
410
- payload: expect.objectContaining({
411
- action: 'SET_FORM_FIELD',
412
- fieldPath: 'form.approvalLevel',
413
- }),
414
- }),
415
- );
416
- expect(fixture.savedInstance).toMatchObject({
417
- formData: { amount: 1200, approvalLevel: '主管簽核' },
418
- state: ApprovalInstanceStateEnum.APPROVED,
419
- });
420
- });
421
-
422
- it('resolves a user task assignee from a selected position', async (): Promise<void> => {
423
- const fixture = createServiceFixture({
424
- currentVersionId: 'template-version-1',
425
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
426
- processMemberships: [
427
- createMembership({
428
- isPrimary: true,
429
- memberId: 'member-position-owner',
430
- positionId: 'position-finance',
431
- }),
432
- ],
433
- processWorkflowSnapshot: createLinearUserTaskWorkflow({
434
- approverResolver: {
435
- positionId: 'position-finance',
436
- type: 'POSITION',
437
- },
438
- }),
439
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
440
- });
441
-
442
- await fixture.service.processInstance('instance-1');
443
-
444
- expect(fixture.savedTasks[0]).toMatchObject({
445
- assigneeMemberId: 'member-position-owner',
446
- originalAssigneeMemberId: 'member-position-owner',
447
- });
448
- });
449
-
450
- it('resolves a user task assignee from the initiator organization manager', async (): Promise<void> => {
451
- const fixture = createServiceFixture({
452
- currentVersionId: 'template-version-1',
453
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
454
- processManagerResolutions: [
455
- createManagerResolution({
456
- managerMemberId: 'member-manager',
457
- scopeId: 'org-finance',
458
- scopeType: ManagerResolutionScopeTypeEnum.ORG_UNIT,
459
- }),
460
- ],
461
- processMemberships: [
462
- createMembership({
463
- memberId: 'member-001',
464
- orgUnitId: 'org-finance',
465
- }),
466
- ],
467
- processOrgUnits: [
468
- createOrgUnit({
469
- id: 'org-finance',
470
- path: 'org.finance',
471
- }),
472
- ],
473
- processWorkflowSnapshot: createLinearUserTaskWorkflow({
474
- approverResolver: {
475
- baseFromInitiator: true,
476
- levelsUp: 1,
477
- type: 'ORG_MANAGER',
478
- },
479
- }),
480
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
481
- });
482
-
483
- await fixture.service.processInstance('instance-1');
484
-
485
- expect(fixture.savedTasks[0]).toMatchObject({
486
- assigneeMemberId: 'member-manager',
487
- originalAssigneeMemberId: 'member-manager',
488
- });
489
- });
490
-
491
- it('stops with a clear error when the initiator manager cannot be resolved', async (): Promise<void> => {
492
- const fixture = createServiceFixture({
493
- currentVersionId: 'template-version-1',
494
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
495
- processWorkflowSnapshot: createLinearUserTaskWorkflow({
496
- approverResolver: {
497
- baseFromInitiator: true,
498
- levelsUp: 1,
499
- type: 'ORG_MANAGER',
500
- },
501
- }),
502
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
503
- });
504
-
505
- await expect(fixture.service.processInstance('instance-1')).rejects.toThrow(
506
- '簽核節點「財務簽核」 無法建立待簽任務:找不到發起人的第 1 層主管,且未設定改派固定人。',
507
- );
508
- });
509
-
510
- it('uses a fixed fallback assignee when the initiator manager cannot be resolved', async (): Promise<void> => {
511
- const fixture = createServiceFixture({
512
- currentVersionId: 'template-version-1',
513
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
514
- processWorkflowSnapshot: createLinearUserTaskWorkflow({
515
- approverResolver: {
516
- baseFromInitiator: true,
517
- fallback: {
518
- memberId: 'member-admin',
519
- type: 'DIRECT',
520
- },
521
- levelsUp: 1,
522
- type: 'ORG_MANAGER',
523
- },
524
- }),
525
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
526
- });
527
-
528
- await fixture.service.processInstance('instance-1');
529
-
530
- expect(fixture.savedTasks[0]).toMatchObject({
531
- assigneeMemberId: 'member-admin',
532
- originalAssigneeMemberId: 'member-admin',
533
- });
534
- });
535
-
536
- it('rejects self fallback unless the workflow explicitly allows initiator self approval', async (): Promise<void> => {
537
- const fixture = createServiceFixture({
538
- currentVersionId: 'template-version-1',
539
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
540
- processWorkflowSnapshot: createLinearUserTaskWorkflow({
541
- approverResolver: {
542
- baseFromInitiator: true,
543
- fallback: {
544
- memberId: 'member-001',
545
- type: 'DIRECT',
546
- },
547
- levelsUp: 1,
548
- type: 'ORG_MANAGER',
549
- },
550
- }),
551
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
552
- });
553
-
554
- await expect(fixture.service.processInstance('instance-1')).rejects.toThrow(
555
- '改派固定人是申請人本人',
556
- );
557
- });
558
-
559
- it('allows self fallback only when the workflow explicitly allows initiator self approval', async (): Promise<void> => {
560
- const fixture = createServiceFixture({
561
- currentVersionId: 'template-version-1',
562
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
563
- processWorkflowSnapshot: createLinearUserTaskWorkflow({
564
- approverResolver: {
565
- baseFromInitiator: true,
566
- fallback: {
567
- allowInitiatorSelfApproval: true,
568
- memberId: 'member-001',
569
- type: 'DIRECT',
570
- },
571
- levelsUp: 1,
572
- type: 'ORG_MANAGER',
573
- },
574
- }),
575
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
576
- });
577
-
578
- await fixture.service.processInstance('instance-1');
579
-
580
- expect(fixture.savedTasks[0]).toMatchObject({
581
- assigneeMemberId: 'member-001',
582
- originalAssigneeMemberId: 'member-001',
583
- });
584
- });
585
-
586
- it('resolves a user task assignee from a selected organization manager', async (): Promise<void> => {
587
- const fixture = createServiceFixture({
588
- currentVersionId: 'template-version-1',
589
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
590
- processManagerResolutions: [
591
- createManagerResolution({
592
- managerMemberId: 'member-org-manager',
593
- scopeId: 'org-finance',
594
- scopeType: ManagerResolutionScopeTypeEnum.ORG_UNIT,
595
- }),
596
- ],
597
- processOrgUnits: [
598
- createOrgUnit({
599
- id: 'org-finance',
600
- path: 'org.finance',
601
- }),
602
- ],
603
- processWorkflowSnapshot: createLinearUserTaskWorkflow({
604
- approverResolver: {
605
- orgUnitId: 'org-finance',
606
- type: 'ORG_UNIT_MANAGER',
607
- },
608
- }),
609
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
610
- });
611
-
612
- await fixture.service.processInstance('instance-1');
613
-
614
- expect(fixture.savedTasks[0]).toMatchObject({
615
- assigneeMemberId: 'member-org-manager',
616
- originalAssigneeMemberId: 'member-org-manager',
617
- });
618
- });
619
-
620
- it('applies delegation when creating a user task', async (): Promise<void> => {
621
- const fixture = createServiceFixture({
622
- currentVersionId: 'template-version-1',
623
- delegationResolution: {
624
- delegationChain: [
625
- {
626
- from: 'member-finance',
627
- reason: 'ALL',
628
- ruleId: 'delegation-rule-1',
629
- to: 'member-101',
630
- },
631
- ],
632
- finalAssigneeMemberId: 'member-101',
633
- },
634
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
635
- processWorkflowSnapshot: createLinearUserTaskWorkflow(),
636
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
637
- });
638
-
639
- await fixture.service.processInstance('instance-1');
640
-
641
- expect(fixture.savedTasks).toEqual([
642
- expect.objectContaining({
643
- assigneeMemberId: 'member-101',
644
- delegationChain: [
645
- {
646
- from: 'member-finance',
647
- reason: 'ALL',
648
- ruleId: 'delegation-rule-1',
649
- to: 'member-101',
650
- },
651
- ],
652
- originalAssigneeMemberId: 'member-finance',
653
- }),
654
- ]);
655
- expect(fixture.savedSingleActivityLogs).toContainEqual(
656
- expect.objectContaining({
657
- eventType: ActivityLogEventTypeEnum.TASK_CREATED,
658
- payload: expect.objectContaining({
659
- assigneeMemberId: 'member-101',
660
- originalAssigneeMemberId: 'member-finance',
661
- }),
662
- }),
663
- );
664
- });
665
-
666
- it('approves a task and completes the linear instance', async (): Promise<void> => {
667
- const fixture = createServiceFixture({
668
- currentVersionId: 'template-version-1',
669
- decisionTask: createTask({
670
- nodeId: 'task_finance',
671
- status: TaskStatusEnum.PENDING,
672
- tokenId: 'token-1',
673
- }),
674
- decisionToken: createWorkflowToken({
675
- currentNodeId: 'task_finance',
676
- status: WorkflowTokenStatusEnum.WAITING,
677
- }),
678
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
679
- processWorkflowSnapshot: createLinearUserTaskWorkflow(),
680
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
681
- });
682
-
683
- const decision = await fixture.service.decideTask({
684
- action: TaskDecisionActionEnum.APPROVED,
685
- comment: '同意',
686
- decidedByMemberId: 'member-finance',
687
- taskId: 'task-1',
688
- });
689
-
690
- expect(decision).toMatchObject({
691
- action: TaskDecisionActionEnum.APPROVED,
692
- comment: '同意',
693
- decidedByMemberId: 'member-finance',
694
- taskId: 'task-1',
695
- });
696
- expect(fixture.savedProcessToken).toMatchObject({
697
- currentNodeId: 'end',
698
- status: WorkflowTokenStatusEnum.CONSUMED,
699
- });
700
- expect(fixture.savedInstance).toMatchObject({
701
- completedAt: expect.any(Date),
702
- state: ApprovalInstanceStateEnum.APPROVED,
703
- });
704
- expect(
705
- fixture.notificationService.createInstanceCompletedNotification,
706
- ).toHaveBeenCalledWith(
707
- expect.objectContaining({
708
- instance: expect.objectContaining({
709
- id: 'instance-1',
710
- state: ApprovalInstanceStateEnum.APPROVED,
711
- }),
712
- }),
713
- );
714
- expect(
715
- fixture.notificationService.resolveTaskNotifications,
716
- ).toHaveBeenCalledWith(
717
- expect.objectContaining({
718
- resolution: NotificationResolutionEnum.APPROVED,
719
- supersedeOthers: true,
720
- }),
721
- );
722
- });
723
-
724
- it('requires a rejection comment before rejecting a task', async (): Promise<void> => {
725
- const fixture = createServiceFixture({
726
- currentVersionId: 'template-version-1',
727
- decisionTask: createTask({
728
- nodeId: 'task_finance',
729
- status: TaskStatusEnum.PENDING,
730
- tokenId: 'token-1',
731
- }),
732
- decisionToken: createWorkflowToken({
733
- currentNodeId: 'task_finance',
734
- status: WorkflowTokenStatusEnum.WAITING,
735
- }),
736
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
737
- processWorkflowSnapshot: createLinearUserTaskWorkflow(),
738
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
739
- });
740
-
741
- await expect(
742
- fixture.service.decideTask({
743
- action: TaskDecisionActionEnum.REJECTED,
744
- comment: ' ',
745
- decidedByMemberId: 'member-finance',
746
- taskId: 'task-1',
747
- }),
748
- ).rejects.toThrow('Reject decision comment is required');
749
- });
750
-
751
- it('rejects a task with a rejection comment', async (): Promise<void> => {
752
- const fixture = createServiceFixture({
753
- currentVersionId: 'template-version-1',
754
- decisionTask: createTask({
755
- nodeId: 'task_finance',
756
- status: TaskStatusEnum.PENDING,
757
- tokenId: 'token-1',
758
- }),
759
- decisionToken: createWorkflowToken({
760
- currentNodeId: 'task_finance',
761
- status: WorkflowTokenStatusEnum.WAITING,
762
- }),
763
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
764
- processWorkflowSnapshot: createLinearUserTaskWorkflow(),
765
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
766
- });
767
-
768
- const decision = await fixture.service.decideTask({
769
- action: TaskDecisionActionEnum.REJECTED,
770
- comment: ' 資料不足,請補件 ',
771
- decidedByMemberId: 'member-finance',
772
- taskId: 'task-1',
773
- });
774
-
775
- expect(decision).toMatchObject({
776
- action: TaskDecisionActionEnum.REJECTED,
777
- comment: '資料不足,請補件',
778
- decidedByMemberId: 'member-finance',
779
- taskId: 'task-1',
780
- });
781
- expect(fixture.savedSingleActivityLogs).toContainEqual(
782
- expect.objectContaining({
783
- eventType: ActivityLogEventTypeEnum.TASK_DECIDED,
784
- payload: expect.objectContaining({
785
- action: TaskDecisionActionEnum.REJECTED,
786
- comment: '資料不足,請補件',
787
- }),
788
- }),
789
- );
790
- expect(fixture.savedInstance).toMatchObject({
791
- completedAt: expect.any(Date),
792
- state: ApprovalInstanceStateEnum.REJECTED,
793
- });
794
- expect(
795
- fixture.notificationService.resolveTaskNotifications,
796
- ).toHaveBeenCalledWith(
797
- expect.objectContaining({
798
- resolution: NotificationResolutionEnum.REJECTED,
799
- supersedeOthers: true,
800
- }),
801
- );
802
- });
803
-
804
- it('transfers a task without advancing the token', async (): Promise<void> => {
805
- const fixture = createServiceFixture({
806
- currentVersionId: 'template-version-1',
807
- decisionTask: createTask({
808
- assigneeMemberId: 'member-finance',
809
- nodeId: 'task_finance',
810
- originalAssigneeMemberId: 'member-finance',
811
- status: TaskStatusEnum.PENDING,
812
- tokenId: 'token-1',
813
- }),
814
- decisionToken: createWorkflowToken({
815
- currentNodeId: 'task_finance',
816
- status: WorkflowTokenStatusEnum.WAITING,
817
- }),
818
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
819
- processWorkflowSnapshot: createLinearUserTaskWorkflow(),
820
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
821
- });
822
-
823
- const decision = await fixture.service.decideTask({
824
- action: TaskDecisionActionEnum.TRANSFERRED,
825
- comment: '請改由財務主管處理',
826
- decidedByMemberId: 'member-finance',
827
- taskId: 'task-1',
828
- transferToMemberId: 'member-101',
829
- });
830
-
831
- expect(decision).toMatchObject({
832
- action: TaskDecisionActionEnum.TRANSFERRED,
833
- transferToMemberId: 'member-101',
834
- });
835
- expect(fixture.savedTasks).toEqual(
836
- expect.arrayContaining([
837
- expect.objectContaining({
838
- id: 'task-1',
839
- status: TaskStatusEnum.TRANSFERRED,
840
- }),
841
- expect.objectContaining({
842
- assigneeMemberId: 'member-101',
843
- delegationChain: [
844
- {
845
- from: 'member-finance',
846
- reason: 'MANUAL_TRANSFER',
847
- ruleId: null,
848
- to: 'member-101',
849
- },
850
- ],
851
- originalAssigneeMemberId: 'member-finance',
852
- status: TaskStatusEnum.PENDING,
853
- tokenId: 'token-1',
854
- }),
855
- ]),
856
- );
857
- expect(fixture.savedProcessToken).toBeNull();
858
- expect(fixture.savedActivityLogs.map((log) => log.eventType)).toEqual([
859
- ActivityLogEventTypeEnum.TASK_DECIDED,
860
- ActivityLogEventTypeEnum.TASK_CREATED,
861
- ]);
862
- expect(
863
- fixture.notificationService.resolveTaskNotifications,
864
- ).toHaveBeenCalledWith(
865
- expect.objectContaining({
866
- resolution: NotificationResolutionEnum.TRANSFERRED,
867
- supersedeOthers: true,
868
- }),
869
- );
870
- });
871
-
872
- it('lists pending inbox tasks by assignee', async (): Promise<void> => {
873
- const fixture = createServiceFixture({
874
- currentVersionId: 'template-version-1',
875
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
876
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
877
- });
878
-
879
- await fixture.service.listInboxTasks('member-finance');
880
-
881
- expect(fixture.rootTaskFind).toHaveBeenCalledWith({
882
- order: { createdAt: 'DESC' },
883
- where: [
884
- {
885
- assigneeMemberId: 'member-finance',
886
- status: expect.any(Object),
887
- },
888
- ],
889
- });
890
- });
891
-
892
- it('lists completed approval history tasks by assignee', async (): Promise<void> => {
893
- const fixture = createServiceFixture({
894
- currentVersionId: 'template-version-1',
895
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
896
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
897
- });
898
-
899
- await fixture.service.listApprovalHistoryTasks('member-finance');
900
-
901
- expect(fixture.rootTaskFind).toHaveBeenCalledWith({
902
- order: { completedAt: 'DESC', createdAt: 'DESC' },
903
- take: 50,
904
- where: {
905
- assigneeMemberId: 'member-finance',
906
- status: TaskStatusEnum.COMPLETED,
907
- },
908
- });
909
- });
910
-
911
- it('counts and pages filtered approval instances without loading an unpaged list in the client', async (): Promise<void> => {
912
- const fixture = createServiceFixture({
913
- currentVersionId: 'template-version-1',
914
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
915
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
916
- });
917
- const instances = [
918
- createApprovalInstance({
919
- id: 'purchase-1',
920
- title: '採購請款 A',
921
- }),
922
- createApprovalInstance({
923
- id: 'purchase-2',
924
- title: '採購請款 B',
925
- }),
926
- createApprovalInstance({
927
- id: 'leave-1',
928
- title: '請假申請',
929
- }),
930
- ];
931
-
932
- fixture.rootInstanceFind.mockResolvedValue(instances);
933
- fixture.rootInstanceQueryBuilder.getMany.mockResolvedValue([instances[0]]);
934
- fixture.rootInstanceCount.mockResolvedValue(2);
935
-
936
- await expect(
937
- fixture.service.listApprovalInstances(undefined, {
938
- page: 1,
939
- pageSize: 1,
940
- searchText: '採購',
941
- view: ApprovalInstanceListViewEnum.ALL,
942
- }),
943
- ).resolves.toEqual([instances[0]]);
944
- await expect(
945
- fixture.service.countApprovalInstances(undefined, {
946
- searchText: '採購',
947
- view: ApprovalInstanceListViewEnum.ALL,
948
- }),
949
- ).resolves.toBe(2);
950
- await expect(
951
- fixture.service.readApprovalInstancePageInfo(undefined, {
952
- page: 1,
953
- pageSize: 1,
954
- searchText: '採購',
955
- view: ApprovalInstanceListViewEnum.ALL,
956
- }),
957
- ).resolves.toEqual({
958
- hasNextPage: true,
959
- hasPreviousPage: false,
960
- page: 1,
961
- pageSize: 1,
962
- totalCount: 2,
963
- totalPages: 2,
964
- });
965
- expect(fixture.rootInstanceQueryBuilder.skip).toHaveBeenCalledWith(0);
966
- expect(fixture.rootInstanceQueryBuilder.take).toHaveBeenCalledWith(1);
967
- expect(fixture.rootInstanceQueryBuilder.getMany).toHaveBeenCalledTimes(1);
968
- expect(fixture.rootInstanceCount).toHaveBeenCalledTimes(2);
969
- });
970
-
971
- it('filters readable approval instances with SQL EXISTS conditions instead of loading readable id lists', async (): Promise<void> => {
972
- const fixture = createServiceFixture({
973
- currentVersionId: 'template-version-1',
974
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
975
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
976
- });
977
-
978
- await fixture.service.listApprovalInstances(
979
- createAuthContext('member-finance'),
980
- {
981
- page: 1,
982
- pageSize: 20,
983
- view: ApprovalInstanceListViewEnum.ALL,
984
- },
985
- );
986
-
987
- expect(fixture.rootInstanceQueryBuilder.andWhere).toHaveBeenCalledWith(
988
- expect.stringContaining('FROM "task_candidates"'),
989
- { readableMemberId: 'member-finance' },
990
- );
991
- expect(fixture.rootInstanceQueryBuilder.andWhere).toHaveBeenCalledWith(
992
- expect.stringContaining('FROM "task_decisions"'),
993
- { readableMemberId: 'member-finance' },
994
- );
995
- expect(fixture.rootTaskFind).not.toHaveBeenCalled();
996
- expect(fixture.notificationFind).not.toHaveBeenCalled();
997
- });
998
-
999
- it('filters CC approval instances with a notification EXISTS condition', async (): Promise<void> => {
1000
- const fixture = createServiceFixture({
1001
- currentVersionId: 'template-version-1',
1002
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1003
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1004
- });
1005
-
1006
- await fixture.service.listApprovalInstances(
1007
- createAuthContext('member-finance'),
1008
- {
1009
- view: ApprovalInstanceListViewEnum.CC,
1010
- },
1011
- );
1012
-
1013
- expect(fixture.rootInstanceQueryBuilder.andWhere).toHaveBeenCalledWith(
1014
- expect.stringContaining('FROM "notifications"'),
1015
- { readableMemberId: 'member-finance' },
1016
- );
1017
- expect(fixture.notificationFind).not.toHaveBeenCalled();
1018
- });
1019
-
1020
- it('routes an exclusive gateway through the first matching condition', async (): Promise<void> => {
1021
- const fixture = createServiceFixture({
1022
- currentVersionId: 'template-version-1',
1023
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1024
- processFormData: { amount: 1500 },
1025
- processWorkflowSnapshot: createExclusiveGatewayWorkflow(),
1026
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1027
- });
1028
-
1029
- await fixture.service.processInstance('instance-1');
1030
-
1031
- expect(fixture.savedTasks).toEqual([
1032
- expect.objectContaining({
1033
- assigneeMemberId: 'member-high',
1034
- nodeId: 'task_high',
1035
- status: TaskStatusEnum.PENDING,
1036
- }),
1037
- ]);
1038
- });
1039
-
1040
- it('completes an exclusive gateway branch after the selected task is approved', async (): Promise<void> => {
1041
- const fixture = createServiceFixture({
1042
- currentVersionId: 'template-version-1',
1043
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1044
- processFormData: { amount: 1500 },
1045
- processWorkflowSnapshot: createExclusiveGatewayWorkflow(),
1046
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1047
- });
1048
-
1049
- await fixture.service.processInstance('instance-1');
1050
-
1051
- const taskHigh = readTaskByNodeId(fixture.savedTasks, 'task_high');
1052
-
1053
- await fixture.service.decideTask({
1054
- action: TaskDecisionActionEnum.APPROVED,
1055
- comment: null,
1056
- decidedByMemberId: taskHigh.assigneeMemberId ?? '',
1057
- taskId: taskHigh.id,
1058
- });
1059
-
1060
- expect(fixture.savedInstance).toMatchObject({
1061
- state: ApprovalInstanceStateEnum.APPROVED,
1062
- });
1063
- expect(fixture.savedWorkflowTokens).toEqual(
1064
- expect.arrayContaining([
1065
- expect.objectContaining({
1066
- currentNodeId: 'end',
1067
- status: WorkflowTokenStatusEnum.CONSUMED,
1068
- }),
1069
- ]),
1070
- );
1071
- });
1072
-
1073
- it('routes an exclusive gateway through the default edge when no condition matches', async (): Promise<void> => {
1074
- const fixture = createServiceFixture({
1075
- currentVersionId: 'template-version-1',
1076
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1077
- processFormData: { amount: 500 },
1078
- processWorkflowSnapshot: createExclusiveGatewayWorkflow(),
1079
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1080
- });
1081
-
1082
- await fixture.service.processInstance('instance-1');
1083
-
1084
- expect(fixture.savedTasks).toEqual([
1085
- expect.objectContaining({
1086
- assigneeMemberId: 'member-default',
1087
- nodeId: 'task_default',
1088
- status: TaskStatusEnum.PENDING,
1089
- }),
1090
- ]);
1091
- });
1092
-
1093
- it('routes an exclusive gateway with a pure CEL edge expression', async (): Promise<void> => {
1094
- const fixture = createServiceFixture({
1095
- currentVersionId: 'template-version-1',
1096
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1097
- processFormData: { amount: 1500 },
1098
- processWorkflowSnapshot: createExclusiveGatewayWorkflow({
1099
- highCondition:
1100
- 'form.amount > 1000 && initiator.memberId == "member-001"',
1101
- includeStructuredCondition: false,
1102
- }),
1103
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1104
- });
1105
-
1106
- await fixture.service.processInstance('instance-1');
1107
-
1108
- expect(fixture.savedTasks).toEqual([
1109
- expect.objectContaining({
1110
- assigneeMemberId: 'member-high',
1111
- nodeId: 'task_high',
1112
- status: TaskStatusEnum.PENDING,
1113
- }),
1114
- ]);
1115
- });
1116
-
1117
- it('reports dry run edge labels, default routing, and entry condition results', (): void => {
1118
- const fixture = createServiceFixture({
1119
- currentVersionId: 'template-version-1',
1120
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1121
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1122
- });
1123
-
1124
- const result = fixture.service.dryRunApprovalWorkflow({
1125
- formDataJson: '{"amount":500}',
1126
- initiatorMemberId: 'member-001',
1127
- initiatorMetadataSnapshotJson: null,
1128
- workflowDefinitionJson: JSON.stringify(createExclusiveGatewayWorkflow()),
1129
- });
1130
-
1131
- expect(result.valid).toBe(true);
1132
- expect(result.steps).toEqual(
1133
- expect.arrayContaining([
1134
- expect.objectContaining({
1135
- edgeDefault: true,
1136
- edgeLabel: '其他情況',
1137
- edgeMatched: true,
1138
- edgeReason: '其他條件不符合時採用預設路徑。',
1139
- nodeId: 'task_default',
1140
- status: 'WAITING',
1141
- }),
1142
- ]),
1143
- );
1144
- });
1145
-
1146
- it('reports dry run matches for pure CEL edge expressions', (): void => {
1147
- const fixture = createServiceFixture({
1148
- currentVersionId: 'template-version-1',
1149
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1150
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1151
- });
1152
-
1153
- const result = fixture.service.dryRunApprovalWorkflow({
1154
- formDataJson: '{"amount":1500}',
1155
- initiatorMemberId: 'member-001',
1156
- initiatorMetadataSnapshotJson: null,
1157
- workflowDefinitionJson: JSON.stringify(
1158
- createExclusiveGatewayWorkflow({
1159
- highCondition:
1160
- 'form.amount > 1000 && initiator.memberId == "member-001"',
1161
- includeStructuredCondition: false,
1162
- }),
1163
- ),
1164
- });
1165
-
1166
- expect(result.valid).toBe(true);
1167
- expect(result.steps).toEqual(
1168
- expect.arrayContaining([
1169
- expect.objectContaining({
1170
- edgeDefault: false,
1171
- edgeLabel: '金額大於 1000',
1172
- edgeMatched: true,
1173
- edgeReason:
1174
- '條件成立:form.amount > 1000 && initiator.memberId == "member-001"',
1175
- nodeId: 'task_high',
1176
- status: 'WAITING',
1177
- }),
1178
- ]),
1179
- );
1180
- });
1181
-
1182
- it('resubmits returned instances from the return point when configured', async (): Promise<void> => {
1183
- const fixture = createServiceFixture({
1184
- currentVersionId: 'template-version-1',
1185
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1186
- instanceState: ApprovalInstanceStateEnum.RETURNED,
1187
- latestReturnActivity: createActivityLog({
1188
- eventType: ActivityLogEventTypeEnum.INSTANCE_RETURNED,
1189
- nodeId: 'task_finance',
1190
- payload: {
1191
- resubmitStrategy: 'FROM_RETURN_POINT',
1192
- returnedFromNodeId: 'task_finance',
1193
- returnToNodeId: 'start',
1194
- taskId: 'task-1',
1195
- },
1196
- }),
1197
- processWorkflowSnapshot: createLinearUserTaskWorkflow(),
1198
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1199
- });
1200
-
1201
- await fixture.service.resubmitApprovalInstance({
1202
- formDataJson: '{"amount":1200}',
1203
- initiatorMemberId: 'member-001',
1204
- instanceId: 'instance-1',
1205
- title: null,
1206
- });
1207
-
1208
- expect(fixture.savedWorkflowTokens).toEqual(
1209
- expect.arrayContaining([
1210
- expect.objectContaining({
1211
- currentNodeId: 'task_finance',
1212
- status: WorkflowTokenStatusEnum.WAITING,
1213
- }),
1214
- ]),
1215
- );
1216
- expect(fixture.savedTasks).toEqual(
1217
- expect.arrayContaining([
1218
- expect.objectContaining({
1219
- nodeId: 'task_finance',
1220
- status: TaskStatusEnum.PENDING,
1221
- }),
1222
- ]),
1223
- );
1224
- expect(fixture.savedSingleActivityLogs).toEqual(
1225
- expect.arrayContaining([
1226
- expect.objectContaining({
1227
- eventType: ActivityLogEventTypeEnum.INSTANCE_RESUBMITTED,
1228
- payload: { resubmitStrategy: 'FROM_RETURN_POINT' },
1229
- }),
1230
- ]),
1231
- );
1232
- });
1233
-
1234
- it('forks a token across multiple outgoing edges and creates parallel tasks', async (): Promise<void> => {
1235
- const fixture = createServiceFixture({
1236
- currentVersionId: 'template-version-1',
1237
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1238
- processWorkflowSnapshot: createParallelApprovalWorkflow('AND'),
1239
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1240
- });
1241
-
1242
- await fixture.service.processInstance('instance-1');
1243
-
1244
- expect(
1245
- fixture.savedTasks.map((task) => ({
1246
- assigneeMemberId: task.assigneeMemberId,
1247
- nodeId: task.nodeId,
1248
- status: task.status,
1249
- })),
1250
- ).toEqual([
1251
- {
1252
- assigneeMemberId: 'member-a',
1253
- nodeId: 'task_a',
1254
- status: TaskStatusEnum.PENDING,
1255
- },
1256
- {
1257
- assigneeMemberId: 'member-b',
1258
- nodeId: 'task_b',
1259
- status: TaskStatusEnum.PENDING,
1260
- },
1261
- ]);
1262
- });
1263
-
1264
- it('waits for every incoming branch before triggering an AND predecessor node', async (): Promise<void> => {
1265
- const fixture = createServiceFixture({
1266
- currentVersionId: 'template-version-1',
1267
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1268
- processWorkflowSnapshot: createParallelApprovalWorkflow('AND'),
1269
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1270
- });
1271
-
1272
- await fixture.service.processInstance('instance-1');
1273
-
1274
- const taskA = readTaskByNodeId(fixture.savedTasks, 'task_a');
1275
- const taskB = readTaskByNodeId(fixture.savedTasks, 'task_b');
1276
-
1277
- await fixture.service.decideTask({
1278
- action: TaskDecisionActionEnum.APPROVED,
1279
- comment: null,
1280
- decidedByMemberId: taskA.assigneeMemberId ?? '',
1281
- taskId: taskA.id,
1282
- });
1283
-
1284
- expect(
1285
- fixture.savedTasks.some((task) => task.nodeId === 'task_final'),
1286
- ).toBe(false);
1287
- expect(
1288
- fixture.savedWorkflowTokens.some(
1289
- (token) =>
1290
- token.currentNodeId === 'task_final' &&
1291
- token.status === WorkflowTokenStatusEnum.WAITING,
1292
- ),
1293
- ).toBe(true);
1294
-
1295
- await fixture.service.decideTask({
1296
- action: TaskDecisionActionEnum.APPROVED,
1297
- comment: null,
1298
- decidedByMemberId: taskB.assigneeMemberId ?? '',
1299
- taskId: taskB.id,
1300
- });
1301
-
1302
- expect(fixture.savedTasks).toEqual(
1303
- expect.arrayContaining([
1304
- expect.objectContaining({
1305
- assigneeMemberId: 'member-final',
1306
- nodeId: 'task_final',
1307
- status: TaskStatusEnum.PENDING,
1308
- }),
1309
- ]),
1310
- );
1311
- });
1312
-
1313
- it('triggers an OR predecessor node from the first completed branch and cancels alternatives', async (): Promise<void> => {
1314
- const fixture = createServiceFixture({
1315
- currentVersionId: 'template-version-1',
1316
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1317
- processWorkflowSnapshot: createParallelApprovalWorkflow('OR'),
1318
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1319
- });
1320
-
1321
- await fixture.service.processInstance('instance-1');
1322
-
1323
- const taskA = readTaskByNodeId(fixture.savedTasks, 'task_a');
1324
- const taskB = readTaskByNodeId(fixture.savedTasks, 'task_b');
1325
-
1326
- await fixture.service.decideTask({
1327
- action: TaskDecisionActionEnum.APPROVED,
1328
- comment: null,
1329
- decidedByMemberId: taskA.assigneeMemberId ?? '',
1330
- taskId: taskA.id,
1331
- });
1332
-
1333
- expect(fixture.savedTasks).toEqual(
1334
- expect.arrayContaining([
1335
- expect.objectContaining({
1336
- assigneeMemberId: 'member-final',
1337
- nodeId: 'task_final',
1338
- status: TaskStatusEnum.PENDING,
1339
- }),
1340
- expect.objectContaining({
1341
- id: taskB.id,
1342
- nodeId: 'task_b',
1343
- status: TaskStatusEnum.CANCELLED,
1344
- }),
1345
- ]),
1346
- );
1347
- });
1348
-
1349
- it('allows an instance initiator to read the approval instance', async (): Promise<void> => {
1350
- const fixture = createServiceFixture({
1351
- currentVersionId: 'template-version-1',
1352
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1353
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1354
- });
1355
-
1356
- await expect(
1357
- fixture.service.getApprovalInstance(
1358
- 'instance-1',
1359
- createAuthContext('member-001'),
1360
- ),
1361
- ).resolves.toMatchObject({ id: 'instance-1' });
1362
- });
1363
-
1364
- it('allows a related task assignee to read the approval instance', async (): Promise<void> => {
1365
- const fixture = createServiceFixture({
1366
- currentVersionId: 'template-version-1',
1367
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1368
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1369
- });
1370
-
1371
- fixture.rootTaskFind.mockResolvedValueOnce([
1372
- createTask({ assigneeMemberId: 'member-202' }),
1373
- ]);
1374
-
1375
- await expect(
1376
- fixture.service.getApprovalInstance(
1377
- 'instance-1',
1378
- createAuthContext('member-202'),
1379
- ),
1380
- ).resolves.toMatchObject({ id: 'instance-1' });
1381
- });
1382
-
1383
- it('hides an approval instance from unrelated members', async (): Promise<void> => {
1384
- const fixture = createServiceFixture({
1385
- currentVersionId: 'template-version-1',
1386
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1387
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1388
- });
1389
-
1390
- await expect(
1391
- fixture.service.getApprovalInstance(
1392
- 'instance-1',
1393
- createAuthContext('member-unrelated'),
1394
- ),
1395
- ).rejects.toThrow('Approval instance instance-1 was not found');
1396
- });
1397
-
1398
- it('rejects ad-hoc countersign requests when the node disallows added signers', async (): Promise<void> => {
1399
- const fixture = createServiceFixture({
1400
- currentVersionId: 'template-version-1',
1401
- decisionTask: createTask({
1402
- nodeId: 'task_finance',
1403
- status: TaskStatusEnum.PENDING,
1404
- tokenId: 'token-1',
1405
- }),
1406
- decisionToken: createWorkflowToken({
1407
- currentNodeId: 'task_finance',
1408
- status: WorkflowTokenStatusEnum.WAITING,
1409
- }),
1410
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1411
- processWorkflowSnapshot: createLinearUserTaskWorkflow(),
1412
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1413
- });
1414
-
1415
- await expect(
1416
- fixture.service.requestAdhocCountersign({
1417
- requestedByMemberId: 'member-finance',
1418
- target: {
1419
- kind: AdhocTargetKindEnum.MEMBER,
1420
- memberIds: ['member-d'],
1421
- },
1422
- taskId: 'task-1',
1423
- }),
1424
- ).rejects.toThrow('does not allow ad-hoc signers');
1425
- });
1426
-
1427
- it('creates a blocking ad-hoc pre-approval task on the current node', async (): Promise<void> => {
1428
- const fixture = createServiceFixture({
1429
- currentVersionId: 'template-version-1',
1430
- decisionTask: createTask({
1431
- nodeId: 'task_finance',
1432
- status: TaskStatusEnum.PENDING,
1433
- tokenId: 'token-1',
1434
- }),
1435
- decisionToken: createWorkflowToken({
1436
- currentNodeId: 'task_finance',
1437
- status: WorkflowTokenStatusEnum.WAITING,
1438
- }),
1439
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1440
- processWorkflowSnapshot: createLinearUserTaskWorkflow({
1441
- allowAddSigner: true,
1442
- }),
1443
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1444
- });
1445
-
1446
- const adhocTask = await fixture.service.requestAdhocPreApproval({
1447
- comment: '請先確認預算',
1448
- onReject: AdhocPreApprovalRejectBehaviorEnum.REJECT_INSTANCE,
1449
- requestedByMemberId: 'member-finance',
1450
- target: {
1451
- kind: AdhocTargetKindEnum.MEMBER,
1452
- memberIds: ['member-d'],
1453
- },
1454
- taskId: 'task-1',
1455
- });
1456
-
1457
- expect(adhocTask).toMatchObject({
1458
- adhocType: AdhocDirectiveTypeEnum.PRE_APPROVAL,
1459
- assigneeMemberId: 'member-d',
1460
- isAdhoc: true,
1461
- nodeId: 'task_finance',
1462
- status: TaskStatusEnum.PENDING,
1463
- tokenId: 'token-1',
1464
- });
1465
- expect(fixture.savedAdhocDirectives).toContainEqual(
1466
- expect.objectContaining({
1467
- onReject: AdhocPreApprovalRejectBehaviorEnum.REJECT_INSTANCE,
1468
- status: AdhocDirectiveStatusEnum.CONSUMED,
1469
- type: AdhocDirectiveTypeEnum.PRE_APPROVAL,
1470
- }),
1471
- );
1472
- expect(
1473
- fixture.notificationService.createTaskAssignedNotification,
1474
- ).toHaveBeenCalledWith(
1475
- expect.objectContaining({
1476
- task: expect.objectContaining({ assigneeMemberId: 'member-d' }),
1477
- }),
1478
- );
1479
- });
1480
-
1481
- it('holds the token until every ad-hoc task on the node is approved', async (): Promise<void> => {
1482
- const fixture = createServiceFixture({
1483
- additionalProcessTasks: [
1484
- createTask({
1485
- adhocDirectiveId: 'directive-seed-1',
1486
- adhocOriginTaskId: 'task-1',
1487
- adhocType: AdhocDirectiveTypeEnum.PRE_APPROVAL,
1488
- assigneeMemberId: 'member-d',
1489
- id: 'task-90',
1490
- isAdhoc: true,
1491
- nodeId: 'task_finance',
1492
- originalAssigneeMemberId: 'member-d',
1493
- status: TaskStatusEnum.PENDING,
1494
- tokenId: 'token-1',
1495
- }),
1496
- ],
1497
- currentVersionId: 'template-version-1',
1498
- decisionTask: createTask({
1499
- nodeId: 'task_finance',
1500
- status: TaskStatusEnum.PENDING,
1501
- tokenId: 'token-1',
1502
- }),
1503
- decisionToken: createWorkflowToken({
1504
- currentNodeId: 'task_finance',
1505
- status: WorkflowTokenStatusEnum.WAITING,
1506
- }),
1507
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1508
- processWorkflowSnapshot: createLinearUserTaskWorkflow({
1509
- allowAddSigner: true,
1510
- }),
1511
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1512
- });
1513
-
1514
- await fixture.service.decideTask({
1515
- action: TaskDecisionActionEnum.APPROVED,
1516
- comment: null,
1517
- decidedByMemberId: 'member-finance',
1518
- taskId: 'task-1',
1519
- });
1520
-
1521
- // The origin task completed but the ad-hoc pre-approval is still open —
1522
- // the token must not advance and the instance must stay running.
1523
- expect(fixture.savedProcessToken).toBeNull();
1524
- expect(fixture.savedInstance).toBeNull();
1525
-
1526
- await fixture.service.decideTask({
1527
- action: TaskDecisionActionEnum.APPROVED,
1528
- comment: null,
1529
- decidedByMemberId: 'member-d',
1530
- taskId: 'task-90',
1531
- });
1532
-
1533
- expect(fixture.savedProcessToken).toMatchObject({
1534
- currentNodeId: 'end',
1535
- status: WorkflowTokenStatusEnum.CONSUMED,
1536
- });
1537
- expect(fixture.savedInstance).toMatchObject({
1538
- state: ApprovalInstanceStateEnum.APPROVED,
1539
- });
1540
- });
1541
-
1542
- it('spawns a parallel countersign task when the next user task is created', async (): Promise<void> => {
1543
- const fixture = createServiceFixture({
1544
- currentVersionId: 'template-version-1',
1545
- decisionToken: createWorkflowToken({
1546
- currentNodeId: 'start',
1547
- status: WorkflowTokenStatusEnum.ACTIVE,
1548
- }),
1549
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1550
- processAdhocDirectives: [
1551
- createAdhocDirective({
1552
- id: 'directive-seed-1',
1553
- originNodeId: 'task_origin',
1554
- status: AdhocDirectiveStatusEnum.PENDING,
1555
- type: AdhocDirectiveTypeEnum.COUNTERSIGN,
1556
- }),
1557
- ],
1558
- processWorkflowSnapshot: createLinearUserTaskWorkflow({
1559
- allowAddSigner: true,
1560
- }),
1561
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1562
- });
1563
-
1564
- await fixture.service.processInstance('instance-1');
1565
-
1566
- const mainTask = fixture.savedTasks.find(
1567
- (task) => !task.isAdhoc && task.nodeId === 'task_finance',
1568
- );
1569
- const countersignTask = fixture.savedTasks.find(
1570
- (task) =>
1571
- task.isAdhoc && task.adhocType === AdhocDirectiveTypeEnum.COUNTERSIGN,
1572
- );
1573
-
1574
- expect(mainTask).toMatchObject({
1575
- assigneeMemberId: 'member-finance',
1576
- status: TaskStatusEnum.PENDING,
1577
- });
1578
- expect(countersignTask).toMatchObject({
1579
- adhocDirectiveId: 'directive-seed-1',
1580
- assigneeMemberId: 'member-d',
1581
- nodeId: 'task_finance',
1582
- status: TaskStatusEnum.PENDING,
1583
- });
1584
- expect(fixture.savedAdhocDirectives).toContainEqual(
1585
- expect.objectContaining({
1586
- id: 'directive-seed-1',
1587
- status: AdhocDirectiveStatusEnum.CONSUMED,
1588
- }),
1589
- );
1590
- });
1591
-
1592
- it('returns a rejected ad-hoc pre-approval to the origin approver without rejecting the instance', async (): Promise<void> => {
1593
- const fixture = createServiceFixture({
1594
- additionalProcessTasks: [
1595
- createTask({
1596
- adhocDirectiveId: 'directive-seed-1',
1597
- adhocOriginTaskId: 'task-1',
1598
- adhocType: AdhocDirectiveTypeEnum.PRE_APPROVAL,
1599
- assigneeMemberId: 'member-d',
1600
- id: 'task-90',
1601
- isAdhoc: true,
1602
- nodeId: 'task_finance',
1603
- originalAssigneeMemberId: 'member-d',
1604
- status: TaskStatusEnum.PENDING,
1605
- tokenId: 'token-1',
1606
- }),
1607
- ],
1608
- currentVersionId: 'template-version-1',
1609
- decisionTask: createTask({
1610
- nodeId: 'task_finance',
1611
- status: TaskStatusEnum.PENDING,
1612
- tokenId: 'token-1',
1613
- }),
1614
- decisionToken: createWorkflowToken({
1615
- currentNodeId: 'task_finance',
1616
- status: WorkflowTokenStatusEnum.WAITING,
1617
- }),
1618
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1619
- processAdhocDirectives: [
1620
- createAdhocDirective({
1621
- consumedAt: new Date('2026-05-04T09:00:00.000Z'),
1622
- id: 'directive-seed-1',
1623
- onReject: AdhocPreApprovalRejectBehaviorEnum.RETURN_TO_ORIGIN,
1624
- status: AdhocDirectiveStatusEnum.CONSUMED,
1625
- type: AdhocDirectiveTypeEnum.PRE_APPROVAL,
1626
- }),
1627
- ],
1628
- processWorkflowSnapshot: createLinearUserTaskWorkflow({
1629
- allowAddSigner: true,
1630
- }),
1631
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1632
- });
1633
-
1634
- const decision = await fixture.service.decideTask({
1635
- action: TaskDecisionActionEnum.REJECTED,
1636
- comment: '需要更多資訊',
1637
- decidedByMemberId: 'member-d',
1638
- taskId: 'task-90',
1639
- });
1640
-
1641
- expect(decision).toMatchObject({
1642
- action: TaskDecisionActionEnum.REJECTED,
1643
- taskId: 'task-90',
1644
- });
1645
- // RETURN_TO_ORIGIN must not reject the instance.
1646
- expect(fixture.savedInstance).toBeNull();
1647
- expect(
1648
- fixture.notificationService.createAdhocWorkflowNotifications,
1649
- ).toHaveBeenCalledWith(
1650
- expect.objectContaining({
1651
- recipientMemberIds: ['member-finance'],
1652
- }),
1653
- );
1654
- expect(fixture.savedSingleActivityLogs).toContainEqual(
1655
- expect.objectContaining({
1656
- eventType: ActivityLogEventTypeEnum.ADHOC_PRE_APPROVAL_RETURNED,
1657
- }),
1658
- );
1659
- });
1660
-
1661
- it('rejects the whole instance when an ad-hoc pre-approval is rejected with REJECT_INSTANCE', async (): Promise<void> => {
1662
- const fixture = createServiceFixture({
1663
- additionalProcessTasks: [
1664
- createTask({
1665
- adhocDirectiveId: 'directive-seed-1',
1666
- adhocOriginTaskId: 'task-1',
1667
- adhocType: AdhocDirectiveTypeEnum.PRE_APPROVAL,
1668
- assigneeMemberId: 'member-d',
1669
- id: 'task-90',
1670
- isAdhoc: true,
1671
- nodeId: 'task_finance',
1672
- originalAssigneeMemberId: 'member-d',
1673
- status: TaskStatusEnum.PENDING,
1674
- tokenId: 'token-1',
1675
- }),
1676
- ],
1677
- currentVersionId: 'template-version-1',
1678
- decisionTask: createTask({
1679
- nodeId: 'task_finance',
1680
- status: TaskStatusEnum.PENDING,
1681
- tokenId: 'token-1',
1682
- }),
1683
- decisionToken: createWorkflowToken({
1684
- currentNodeId: 'task_finance',
1685
- status: WorkflowTokenStatusEnum.WAITING,
1686
- }),
1687
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1688
- processAdhocDirectives: [
1689
- createAdhocDirective({
1690
- consumedAt: new Date('2026-05-04T09:00:00.000Z'),
1691
- id: 'directive-seed-1',
1692
- onReject: AdhocPreApprovalRejectBehaviorEnum.REJECT_INSTANCE,
1693
- status: AdhocDirectiveStatusEnum.CONSUMED,
1694
- type: AdhocDirectiveTypeEnum.PRE_APPROVAL,
1695
- }),
1696
- ],
1697
- processWorkflowSnapshot: createLinearUserTaskWorkflow({
1698
- allowAddSigner: true,
1699
- }),
1700
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1701
- });
1702
-
1703
- await fixture.service.decideTask({
1704
- action: TaskDecisionActionEnum.REJECTED,
1705
- comment: '不同意',
1706
- decidedByMemberId: 'member-d',
1707
- taskId: 'task-90',
1708
- });
1709
-
1710
- expect(fixture.savedInstance).toMatchObject({
1711
- state: ApprovalInstanceStateEnum.REJECTED,
1712
- });
1713
- });
1714
-
1715
- it('dispatches an ad-hoc stage notification when the stage is approved', async (): Promise<void> => {
1716
- const fixture = createServiceFixture({
1717
- currentVersionId: 'template-version-1',
1718
- decisionTask: createTask({
1719
- nodeId: 'task_finance',
1720
- status: TaskStatusEnum.PENDING,
1721
- tokenId: 'token-1',
1722
- }),
1723
- decisionToken: createWorkflowToken({
1724
- currentNodeId: 'task_finance',
1725
- status: WorkflowTokenStatusEnum.WAITING,
1726
- }),
1727
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1728
- processAdhocDirectives: [
1729
- createAdhocDirective({
1730
- id: 'directive-stage-1',
1731
- originNodeId: 'task_finance',
1732
- status: AdhocDirectiveStatusEnum.PENDING,
1733
- targetValue: {
1734
- kind: AdhocTargetKindEnum.MEMBER,
1735
- memberIds: ['member-x'],
1736
- },
1737
- type: AdhocDirectiveTypeEnum.STAGE_NOTIFY,
1738
- }),
1739
- ],
1740
- processWorkflowSnapshot: createLinearUserTaskWorkflow(),
1741
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1742
- });
1743
-
1744
- await fixture.service.decideTask({
1745
- action: TaskDecisionActionEnum.APPROVED,
1746
- comment: null,
1747
- decidedByMemberId: 'member-finance',
1748
- taskId: 'task-1',
1749
- });
1750
-
1751
- expect(
1752
- fixture.notificationService.createAdhocWorkflowNotifications,
1753
- ).toHaveBeenCalledWith(
1754
- expect.objectContaining({
1755
- payload: expect.objectContaining({ outcome: 'APPROVED' }),
1756
- recipientMemberIds: ['member-x'],
1757
- }),
1758
- );
1759
- expect(fixture.savedAdhocDirectives).toContainEqual(
1760
- expect.objectContaining({
1761
- id: 'directive-stage-1',
1762
- status: AdhocDirectiveStatusEnum.CONSUMED,
1763
- }),
1764
- );
1765
- });
1766
-
1767
- it('dispatches ad-hoc completion notifications on reject and cancels pending flow directives', async (): Promise<void> => {
1768
- const fixture = createServiceFixture({
1769
- currentVersionId: 'template-version-1',
1770
- decisionTask: createTask({
1771
- nodeId: 'task_finance',
1772
- status: TaskStatusEnum.PENDING,
1773
- tokenId: 'token-1',
1774
- }),
1775
- decisionToken: createWorkflowToken({
1776
- currentNodeId: 'task_finance',
1777
- status: WorkflowTokenStatusEnum.WAITING,
1778
- }),
1779
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1780
- processAdhocDirectives: [
1781
- createAdhocDirective({
1782
- id: 'directive-completion-1',
1783
- status: AdhocDirectiveStatusEnum.PENDING,
1784
- targetValue: {
1785
- kind: AdhocTargetKindEnum.MEMBER,
1786
- memberIds: ['member-x'],
1787
- },
1788
- type: AdhocDirectiveTypeEnum.COMPLETION_NOTIFY,
1789
- }),
1790
- createAdhocDirective({
1791
- id: 'directive-countersign-1',
1792
- status: AdhocDirectiveStatusEnum.PENDING,
1793
- type: AdhocDirectiveTypeEnum.COUNTERSIGN,
1794
- }),
1795
- ],
1796
- processWorkflowSnapshot: createLinearUserTaskWorkflow(),
1797
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1798
- });
1799
-
1800
- await fixture.service.decideTask({
1801
- action: TaskDecisionActionEnum.REJECTED,
1802
- comment: '資料不足',
1803
- decidedByMemberId: 'member-finance',
1804
- taskId: 'task-1',
1805
- });
1806
-
1807
- expect(
1808
- fixture.notificationService.createAdhocWorkflowNotifications,
1809
- ).toHaveBeenCalledWith(
1810
- expect.objectContaining({
1811
- payload: expect.objectContaining({
1812
- finalState: ApprovalInstanceStateEnum.REJECTED,
1813
- }),
1814
- recipientMemberIds: ['member-x'],
1815
- }),
1816
- );
1817
- expect(fixture.savedAdhocDirectives).toContainEqual(
1818
- expect.objectContaining({
1819
- id: 'directive-completion-1',
1820
- status: AdhocDirectiveStatusEnum.CONSUMED,
1821
- }),
1822
- );
1823
- expect(fixture.savedAdhocDirectives).toContainEqual(
1824
- expect.objectContaining({
1825
- id: 'directive-countersign-1',
1826
- status: AdhocDirectiveStatusEnum.CANCELLED,
1827
- }),
1828
- );
1829
- });
1830
-
1831
- it('records ad-hoc webhook notification failures without blocking the decision', async (): Promise<void> => {
1832
- const failingDispatcher: BPMWorkflowServiceTaskDispatcher = {
1833
- dispatchWebhook: jest.fn(() =>
1834
- Promise.reject(new Error('webhook unreachable')),
1835
- ),
1836
- };
1837
- const fixture = createServiceFixture({
1838
- currentVersionId: 'template-version-1',
1839
- decisionTask: createTask({
1840
- nodeId: 'task_finance',
1841
- status: TaskStatusEnum.PENDING,
1842
- tokenId: 'token-1',
1843
- }),
1844
- decisionToken: createWorkflowToken({
1845
- currentNodeId: 'task_finance',
1846
- status: WorkflowTokenStatusEnum.WAITING,
1847
- }),
1848
- formVersionStatus: FormDefinitionVersionStatusEnum.PUBLISHED,
1849
- processAdhocDirectives: [
1850
- createAdhocDirective({
1851
- id: 'directive-webhook-1',
1852
- originNodeId: 'task_finance',
1853
- status: AdhocDirectiveStatusEnum.PENDING,
1854
- targetKind: AdhocTargetKindEnum.WEBHOOK,
1855
- targetValue: {
1856
- kind: AdhocTargetKindEnum.WEBHOOK,
1857
- webhookUrl: 'https://example.com/hook',
1858
- },
1859
- type: AdhocDirectiveTypeEnum.STAGE_NOTIFY,
1860
- }),
1861
- ],
1862
- processWorkflowSnapshot: createLinearUserTaskWorkflow(),
1863
- serviceTaskDispatcher: failingDispatcher,
1864
- templateVersionStatus: ApprovalTemplateVersionStatusEnum.PUBLISHED,
1865
- });
1866
-
1867
- await fixture.service.decideTask({
1868
- action: TaskDecisionActionEnum.APPROVED,
1869
- comment: null,
1870
- decidedByMemberId: 'member-finance',
1871
- taskId: 'task-1',
1872
- });
1873
-
1874
- expect(fixture.savedInstance).toMatchObject({
1875
- state: ApprovalInstanceStateEnum.APPROVED,
1876
- });
1877
- expect(fixture.savedSingleActivityLogs).toContainEqual(
1878
- expect.objectContaining({
1879
- eventType: ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
1880
- payload: expect.objectContaining({
1881
- action: 'ADHOC_WEBHOOK',
1882
- directiveId: 'directive-webhook-1',
1883
- }),
1884
- }),
1885
- );
1886
- });
1887
- });
1888
-
1889
- interface ServiceFixture {
1890
- readonly managerQuery: jest.Mock<
1891
- Promise<unknown>,
1892
- [string, readonly unknown[]]
1893
- >;
1894
- readonly notificationService: Pick<
1895
- NotificationService,
1896
- | 'createAdhocWorkflowNotifications'
1897
- | 'createInstanceCompletedNotification'
1898
- | 'createServiceTaskNotifications'
1899
- | 'createTaskAssignedNotification'
1900
- | 'resolveTaskNotifications'
1901
- | 'supersedeInstanceTaskNotifications'
1902
- >;
1903
- readonly notificationFind: jest.Mock<
1904
- Promise<readonly NotificationEntity[]>,
1905
- [Readonly<Record<string, unknown>>]
1906
- >;
1907
- readonly rootInstanceCount: jest.Mock<Promise<number>, []>;
1908
- readonly rootInstanceFind: jest.Mock<
1909
- Promise<readonly ApprovalInstanceEntity[]>,
1910
- [Readonly<Record<string, unknown>>?]
1911
- >;
1912
- readonly rootInstanceQueryBuilder: ApprovalInstanceQueryBuilderMock;
1913
- readonly rootTaskFind: jest.Mock<
1914
- Promise<readonly TaskEntity[]>,
1915
- [Readonly<Record<string, unknown>>]
1916
- >;
1917
- readonly savedDecision: TaskDecisionEntity | null;
1918
- readonly savedActivityLogs: readonly ActivityLogEntity[];
1919
- readonly savedInstance: ApprovalInstanceEntity | null;
1920
- readonly savedAdhocDirectives: readonly AdhocDirectiveEntity[];
1921
- readonly savedProcessLog: ActivityLogEntity | null;
1922
- readonly savedProcessToken: WorkflowTokenEntity | null;
1923
- readonly savedSingleActivityLogs: readonly ActivityLogEntity[];
1924
- readonly savedTasks: readonly TaskEntity[];
1925
- readonly savedWorkflowTokens: readonly WorkflowTokenEntity[];
1926
- readonly savedToken: WorkflowTokenEntity | null;
1927
- readonly service: WorkflowEngineService;
1928
- }
1929
-
1930
- interface ApprovalInstanceQueryBuilderMock {
1931
- readonly andWhere: jest.Mock<
1932
- ApprovalInstanceQueryBuilderMock,
1933
- [string, Readonly<Record<string, unknown>>?]
1934
- >;
1935
- readonly getCount: jest.Mock<Promise<number>, []>;
1936
- readonly getMany: jest.Mock<Promise<readonly ApprovalInstanceEntity[]>, []>;
1937
- readonly orderBy: jest.Mock<
1938
- ApprovalInstanceQueryBuilderMock,
1939
- [string, 'ASC' | 'DESC']
1940
- >;
1941
- readonly skip: jest.Mock<ApprovalInstanceQueryBuilderMock, [number]>;
1942
- readonly take: jest.Mock<ApprovalInstanceQueryBuilderMock, [number]>;
1943
- }
1944
-
1945
- function createServiceFixture({
1946
- additionalProcessTasks = [],
1947
- currentVersionId,
1948
- delegationResolution,
1949
- decisionTask,
1950
- decisionToken,
1951
- formSchema,
1952
- formVersionStatus,
1953
- instanceState,
1954
- latestReturnActivity,
1955
- processAdhocDirectives = [],
1956
- processFormData,
1957
- processFormDefinitionSnapshot,
1958
- processManagerResolutions = [],
1959
- processMemberships = [],
1960
- processNotifications = [],
1961
- processOrgUnits = [],
1962
- processWorkflowSnapshot,
1963
- serviceTaskDispatcher,
1964
- templateVersionStatus,
1965
- }: {
1966
- readonly additionalProcessTasks?: readonly TaskEntity[];
1967
- readonly currentVersionId: string | null;
1968
- readonly delegationResolution?: DelegationResolution;
1969
- readonly decisionTask?: TaskEntity;
1970
- readonly decisionToken?: WorkflowTokenEntity;
1971
- readonly formSchema?: FormDefinitionSchema;
1972
- readonly formVersionStatus: FormDefinitionVersionStatusEnum;
1973
- readonly instanceState?: ApprovalInstanceStateEnum;
1974
- readonly latestReturnActivity?: ActivityLogEntity | null;
1975
- readonly processAdhocDirectives?: readonly AdhocDirectiveEntity[];
1976
- readonly processFormData?: Readonly<Record<string, unknown>>;
1977
- readonly processFormDefinitionSnapshot?: Readonly<Record<string, unknown>>;
1978
- readonly processManagerResolutions?: readonly ManagerResolutionEntity[];
1979
- readonly processMemberships?: readonly MembershipEntity[];
1980
- readonly processNotifications?: readonly NotificationEntity[];
1981
- readonly processOrgUnits?: readonly OrgUnitEntity[];
1982
- readonly processWorkflowSnapshot?: WorkflowDefinition;
1983
- readonly serviceTaskDispatcher?: BPMWorkflowServiceTaskDispatcher;
1984
- readonly templateVersionStatus: ApprovalTemplateVersionStatusEnum;
1985
- }): ServiceFixture {
1986
- let savedToken: WorkflowTokenEntity | null = null;
1987
- let tokenSequence = 1;
1988
- let taskSequence = 1;
1989
- let processTokens: readonly WorkflowTokenEntity[] = processWorkflowSnapshot
1990
- ? [
1991
- decisionToken ??
1992
- createWorkflowToken({
1993
- currentNodeId: 'start',
1994
- status: WorkflowTokenStatusEnum.ACTIVE,
1995
- }),
1996
- ]
1997
- : [];
1998
- let processTasks: readonly TaskEntity[] = [
1999
- ...(decisionTask ? [decisionTask] : []),
2000
- ...additionalProcessTasks,
2001
- ];
2002
- let savedProcessToken: WorkflowTokenEntity | null = null;
2003
- let savedDecision: TaskDecisionEntity | null = null;
2004
- let savedInstance: ApprovalInstanceEntity | null = null;
2005
- let savedProcessLog: ActivityLogEntity | null = null;
2006
- let savedActivityLogs: readonly ActivityLogEntity[] = [];
2007
- let savedSingleActivityLogs: readonly ActivityLogEntity[] = [];
2008
- let savedTasks: readonly TaskEntity[] = [];
2009
- const template = createTemplate(currentVersionId);
2010
- const templateVersion = createTemplateVersion(templateVersionStatus);
2011
- const formVersion = createFormVersion(formVersionStatus, formSchema);
2012
- const rootInstanceFind = jest.fn<
2013
- Promise<readonly ApprovalInstanceEntity[]>,
2014
- [Readonly<Record<string, unknown>>?]
2015
- >(() => Promise.resolve([createApprovalInstance()]));
2016
- const rootInstanceCount = jest.fn<Promise<number>, []>(() =>
2017
- rootInstanceFind().then((instances) => instances.length),
2018
- );
2019
- const rootInstanceQueryBuilder = createApprovalInstanceQueryBuilderMock(
2020
- rootInstanceFind,
2021
- rootInstanceCount,
2022
- );
2023
- const rootTaskFind = jest.fn<
2024
- Promise<readonly TaskEntity[]>,
2025
- [Readonly<Record<string, unknown>>]
2026
- >(() => Promise.resolve([]));
2027
- const instanceRepository = createRepository<ApprovalInstanceEntity>({
2028
- createQueryBuilder: jest.fn(() => rootInstanceQueryBuilder),
2029
- find: rootInstanceFind,
2030
- findOne: jest.fn(() => Promise.resolve(createApprovalInstance())),
2031
- });
2032
- const tokenRepository = createRepository<WorkflowTokenEntity>({});
2033
- const taskRepository = createRepository<TaskEntity>({
2034
- find: rootTaskFind,
2035
- });
2036
- const taskCandidateRepository = createRepository<TaskCandidateEntity>({
2037
- find: jest.fn(() => Promise.resolve([])),
2038
- });
2039
- const taskDecisionRepository = createRepository<TaskDecisionEntity>({
2040
- find: jest.fn(() => Promise.resolve([])),
2041
- });
2042
- const notificationFind = jest.fn<
2043
- Promise<readonly NotificationEntity[]>,
2044
- [Readonly<Record<string, unknown>>]
2045
- >(() => Promise.resolve(processNotifications));
2046
- const notificationRepository = createRepository<NotificationEntity>({
2047
- find: notificationFind,
2048
- });
2049
- const activityLogRepository = createRepository<ActivityLogEntity>({});
2050
- let processAdhocDirectiveRows: readonly AdhocDirectiveEntity[] = [
2051
- ...processAdhocDirectives,
2052
- ];
2053
- let directiveSequence = 0;
2054
- const adhocDirectiveRepository = createRepository<AdhocDirectiveEntity>({
2055
- find: jest.fn(() => Promise.resolve(processAdhocDirectiveRows)),
2056
- });
2057
- const transactionalAdhocDirectiveRepository =
2058
- createRepository<AdhocDirectiveEntity>({
2059
- create: jest.fn(
2060
- (entity: Partial<AdhocDirectiveEntity>): AdhocDirectiveEntity =>
2061
- Object.assign(new AdhocDirectiveEntity(), entity),
2062
- ),
2063
- find: jest.fn(
2064
- (
2065
- options?: Readonly<{
2066
- where?: Readonly<Record<string, unknown>>;
2067
- }>,
2068
- ): Promise<readonly AdhocDirectiveEntity[]> =>
2069
- Promise.resolve(
2070
- processAdhocDirectiveRows.filter((directive) =>
2071
- Object.entries(options?.where ?? {}).every(
2072
- ([key, value]) =>
2073
- // FindOperator values (e.g. In([...])) are treated as
2074
- // match-all to keep the mock simple.
2075
- typeof value === 'object' ||
2076
- directive[key as keyof AdhocDirectiveEntity] === value,
2077
- ),
2078
- ),
2079
- ),
2080
- ),
2081
- findOne: jest.fn(
2082
- (
2083
- options?: Readonly<{ where?: Readonly<{ id?: string }> }>,
2084
- ): Promise<AdhocDirectiveEntity | null> =>
2085
- Promise.resolve(
2086
- processAdhocDirectiveRows.find(
2087
- (directive) => directive.id === options?.where?.id,
2088
- ) ?? null,
2089
- ),
2090
- ),
2091
- save: jest.fn(
2092
- (
2093
- entityOrEntities: AdhocDirectiveEntity | AdhocDirectiveEntity[],
2094
- ): Promise<AdhocDirectiveEntity | AdhocDirectiveEntity[]> => {
2095
- const entities = Array.isArray(entityOrEntities)
2096
- ? entityOrEntities
2097
- : [entityOrEntities];
2098
- const entitiesWithIds = entities.map((entity) => {
2099
- if (entity.id) {
2100
- return entity;
2101
- }
2102
-
2103
- directiveSequence += 1;
2104
-
2105
- return Object.assign(new AdhocDirectiveEntity(), entity, {
2106
- id: `directive-${directiveSequence}`,
2107
- });
2108
- });
2109
- const entityIds = new Set(
2110
- entitiesWithIds.map((entity) => entity.id),
2111
- );
2112
-
2113
- processAdhocDirectiveRows = [
2114
- ...processAdhocDirectiveRows.filter(
2115
- (directive) => !entityIds.has(directive.id),
2116
- ),
2117
- ...entitiesWithIds,
2118
- ];
2119
-
2120
- return Promise.resolve(
2121
- Array.isArray(entityOrEntities)
2122
- ? entitiesWithIds
2123
- : (entitiesWithIds[0] ?? entityOrEntities),
2124
- );
2125
- },
2126
- ),
2127
- });
2128
- const delegationService = {
2129
- resolveAssignee: jest.fn(
2130
- (assigneeMemberId: string): Promise<DelegationResolution> =>
2131
- Promise.resolve(
2132
- delegationResolution ?? {
2133
- delegationChain: [],
2134
- finalAssigneeMemberId: assigneeMemberId,
2135
- },
2136
- ),
2137
- ),
2138
- };
2139
- const notificationService = {
2140
- createAdhocWorkflowNotifications: jest.fn(() => Promise.resolve([])),
2141
- createInstanceCompletedNotification: jest.fn(() => Promise.resolve([])),
2142
- createServiceTaskNotifications: jest.fn(() => Promise.resolve([])),
2143
- createTaskAssignedNotification: jest.fn(() => Promise.resolve([])),
2144
- resolveTaskNotifications: jest.fn(() => Promise.resolve()),
2145
- supersedeInstanceTaskNotifications: jest.fn(() => Promise.resolve()),
2146
- };
2147
- const attachmentService = {
2148
- bindFormDataAttachmentsToInstance: jest.fn(() => Promise.resolve()),
2149
- };
2150
- const signatureService = {
2151
- signTaskDecision: jest.fn(() =>
2152
- Promise.resolve(
2153
- Object.assign(new SignatureEntity(), {
2154
- id: 'signature-1',
2155
- signedPayloadHash: 'signed-payload-hash-1',
2156
- }),
2157
- ),
2158
- ),
2159
- };
2160
- const templateRepository = createRepository<ApprovalTemplateEntity>({
2161
- findOne: jest.fn(() => Promise.resolve(template)),
2162
- });
2163
- const templateVersionRepository =
2164
- createRepository<ApprovalTemplateVersionEntity>({
2165
- findOne: jest.fn(() => Promise.resolve(templateVersion)),
2166
- });
2167
- const formVersionRepository = createRepository<FormDefinitionVersionEntity>({
2168
- findOne: jest.fn(() => Promise.resolve(formVersion)),
2169
- });
2170
- const transactionalInstanceRepository =
2171
- createRepository<ApprovalInstanceEntity>({
2172
- create: jest.fn(
2173
- (entity: Partial<ApprovalInstanceEntity>): ApprovalInstanceEntity =>
2174
- Object.assign(createApprovalInstance(), entity),
2175
- ),
2176
- findOne: jest.fn(() =>
2177
- Promise.resolve(
2178
- createApprovalInstance({
2179
- formData: processFormData,
2180
- formDefinitionSnapshot: processFormDefinitionSnapshot,
2181
- state: instanceState,
2182
- workflowSnapshot: processWorkflowSnapshot,
2183
- }),
2184
- ),
2185
- ),
2186
- save: jest.fn((entity: ApprovalInstanceEntity) => {
2187
- savedInstance = entity;
2188
-
2189
- return Promise.resolve(entity);
2190
- }),
2191
- });
2192
- const transactionalTokenRepository = createRepository<WorkflowTokenEntity>({
2193
- create: jest.fn(
2194
- (entity: Partial<WorkflowTokenEntity>): WorkflowTokenEntity => ({
2195
- consumedAt: null,
2196
- createdAt: new Date('2026-05-04T09:00:00.000Z'),
2197
- currentNodeId: entity.currentNodeId ?? 'start',
2198
- id: entity.id ?? `token-${(tokenSequence += 1)}`,
2199
- instanceId: entity.instanceId ?? 'instance-1',
2200
- parentTokenId: entity.parentTokenId ?? null,
2201
- status: entity.status ?? WorkflowTokenStatusEnum.ACTIVE,
2202
- }),
2203
- ),
2204
- save: jest.fn(
2205
- (
2206
- entityOrEntities: WorkflowTokenEntity | WorkflowTokenEntity[],
2207
- ): Promise<WorkflowTokenEntity | WorkflowTokenEntity[]> => {
2208
- if (processWorkflowSnapshot) {
2209
- const entities = Array.isArray(entityOrEntities)
2210
- ? entityOrEntities
2211
- : [entityOrEntities];
2212
- const entitiesWithIds = entities.map((entity) => {
2213
- const token = { ...entity };
2214
-
2215
- if (!token.id) {
2216
- tokenSequence += 1;
2217
-
2218
- return { ...token, id: `token-${tokenSequence}` };
2219
- }
2220
-
2221
- if (/^token-\d+$/u.test(token.id)) {
2222
- const idNumber = Number(token.id.replace('token-', ''));
2223
-
2224
- tokenSequence = Math.max(tokenSequence, idNumber);
2225
- }
2226
-
2227
- return token;
2228
- });
2229
- const entityIds = new Set(entitiesWithIds.map((entity) => entity.id));
2230
-
2231
- processTokens = [
2232
- ...processTokens.filter((token) => !entityIds.has(token.id)),
2233
- ...entitiesWithIds,
2234
- ];
2235
- savedProcessToken =
2236
- entitiesWithIds[entitiesWithIds.length - 1] ?? null;
2237
-
2238
- return Promise.resolve(
2239
- Array.isArray(entityOrEntities)
2240
- ? entitiesWithIds
2241
- : (entitiesWithIds[0] ?? entityOrEntities),
2242
- );
2243
- } else {
2244
- savedToken = entityOrEntities as WorkflowTokenEntity;
2245
- }
2246
-
2247
- return Promise.resolve(entityOrEntities);
2248
- },
2249
- ),
2250
- find: jest.fn(() =>
2251
- Promise.resolve([...processTokens].sort(compareTokenCreatedAt)),
2252
- ),
2253
- findOne: jest.fn(
2254
- (
2255
- options?: Readonly<{
2256
- where?: Readonly<{ id?: string }>;
2257
- }>,
2258
- ) =>
2259
- Promise.resolve(
2260
- options?.where?.id
2261
- ? (processTokens.find((token) => token.id === options.where?.id) ??
2262
- null)
2263
- : (processTokens
2264
- .filter(
2265
- (token) => token.status === WorkflowTokenStatusEnum.ACTIVE,
2266
- )
2267
- .sort(compareTokenCreatedAt)[0] ?? null),
2268
- ),
2269
- ),
2270
- });
2271
- const transactionalTaskRepository = createRepository<TaskEntity>({
2272
- create: jest.fn(
2273
- (entity: Partial<TaskEntity>): TaskEntity =>
2274
- Object.assign(new TaskEntity(), {
2275
- adhocDirectiveId: entity.adhocDirectiveId ?? null,
2276
- adhocOriginTaskId: entity.adhocOriginTaskId ?? null,
2277
- adhocType: entity.adhocType ?? null,
2278
- isAdhoc: entity.isAdhoc ?? false,
2279
- assigneeMemberId: entity.assigneeMemberId ?? 'member-finance',
2280
- assignmentType:
2281
- entity.assignmentType ?? TaskAssignmentTypeEnum.DIRECT_MEMBER,
2282
- completedAt: entity.completedAt ?? null,
2283
- createdAt: new Date('2026-05-04T09:00:00.000Z'),
2284
- delegationChain: entity.delegationChain ?? [],
2285
- id: entity.id ?? `task-${(taskSequence += 1)}`,
2286
- instanceId: entity.instanceId ?? 'instance-1',
2287
- nodeId: entity.nodeId ?? 'task_finance',
2288
- openedAt: entity.openedAt ?? null,
2289
- originalAssigneeMemberId:
2290
- entity.originalAssigneeMemberId ?? 'member-finance',
2291
- decisionPolicySnapshot: entity.decisionPolicySnapshot ?? {
2292
- type: 'SINGLE',
2293
- },
2294
- slaDueAt: entity.slaDueAt ?? null,
2295
- status: entity.status ?? TaskStatusEnum.PENDING,
2296
- tokenId: entity.tokenId ?? 'token-1',
2297
- }),
2298
- ),
2299
- find: jest.fn(() => Promise.resolve(processTasks)),
2300
- findOne: jest.fn(
2301
- (
2302
- options?: Readonly<{
2303
- where?: Readonly<{ id?: string; nodeId?: string; tokenId?: string }>;
2304
- }>,
2305
- ) =>
2306
- Promise.resolve(
2307
- options?.where?.id
2308
- ? (processTasks.find((task) => task.id === options.where?.id) ??
2309
- null)
2310
- : options?.where?.tokenId
2311
- ? (processTasks.find(
2312
- (task) =>
2313
- task.tokenId === options.where?.tokenId &&
2314
- (!options.where?.nodeId ||
2315
- task.nodeId === options.where.nodeId),
2316
- ) ?? null)
2317
- : null,
2318
- ),
2319
- ),
2320
- save: jest.fn(
2321
- (
2322
- entityOrEntities: TaskEntity | TaskEntity[],
2323
- ): Promise<TaskEntity | TaskEntity[]> => {
2324
- const entities = Array.isArray(entityOrEntities)
2325
- ? entityOrEntities
2326
- : [entityOrEntities];
2327
- const entitiesWithIds = entities.map((entity) => {
2328
- const task = Object.assign(new TaskEntity(), entity);
2329
-
2330
- if (!task.id) {
2331
- taskSequence += 1;
2332
-
2333
- return Object.assign(new TaskEntity(), task, {
2334
- id: `task-${taskSequence}`,
2335
- });
2336
- }
2337
-
2338
- if (/^task-\d+$/u.test(task.id)) {
2339
- const idNumber = Number(task.id.replace('task-', ''));
2340
-
2341
- taskSequence = Math.max(taskSequence, idNumber);
2342
- }
2343
-
2344
- return task;
2345
- });
2346
- const entityIds = new Set(entitiesWithIds.map((entity) => entity.id));
2347
-
2348
- processTasks = [
2349
- ...processTasks.filter((task) => !entityIds.has(task.id)),
2350
- ...entitiesWithIds,
2351
- ];
2352
- savedTasks = [...savedTasks, ...entitiesWithIds];
2353
-
2354
- return Promise.resolve(
2355
- Array.isArray(entityOrEntities)
2356
- ? entitiesWithIds
2357
- : (entitiesWithIds[0] ?? entityOrEntities),
2358
- );
2359
- },
2360
- ),
2361
- });
2362
- const transactionalTaskCandidateRepository =
2363
- createRepository<TaskCandidateEntity>({
2364
- create: jest.fn(
2365
- (entity: Partial<TaskCandidateEntity>): TaskCandidateEntity =>
2366
- Object.assign(new TaskCandidateEntity(), {
2367
- claimedAt: entity.claimedAt ?? null,
2368
- createdAt: entity.createdAt ?? new Date('2026-05-04T09:00:00.000Z'),
2369
- decidedAt: entity.decidedAt ?? null,
2370
- delegationChain: entity.delegationChain ?? [],
2371
- id: entity.id ?? 'task-candidate-1',
2372
- memberId: entity.memberId ?? 'member-finance',
2373
- originalMemberId: entity.originalMemberId ?? 'member-finance',
2374
- sourceType: entity.sourceType ?? 'DIRECT',
2375
- status: entity.status ?? TaskCandidateStatusEnum.PENDING,
2376
- taskId: entity.taskId ?? 'task-1',
2377
- }),
2378
- ),
2379
- find: jest.fn(() => Promise.resolve([])),
2380
- save: jest.fn(
2381
- (
2382
- entityOrEntities: TaskCandidateEntity | TaskCandidateEntity[],
2383
- ): Promise<TaskCandidateEntity | TaskCandidateEntity[]> =>
2384
- Promise.resolve(entityOrEntities),
2385
- ),
2386
- });
2387
- const transactionalTaskDecisionRepository =
2388
- createRepository<TaskDecisionEntity>({
2389
- create: jest.fn(
2390
- (entity: Partial<TaskDecisionEntity>): TaskDecisionEntity =>
2391
- Object.assign(new TaskDecisionEntity(), {
2392
- action: entity.action ?? TaskDecisionActionEnum.APPROVED,
2393
- comment: entity.comment ?? null,
2394
- decidedAt: entity.decidedAt ?? new Date('2026-05-04T09:00:00.000Z'),
2395
- decidedByMemberId: entity.decidedByMemberId ?? 'member-finance',
2396
- id: entity.id ?? 'decision-1',
2397
- returnToNodeId: entity.returnToNodeId ?? null,
2398
- signatureId: entity.signatureId ?? null,
2399
- taskId: entity.taskId ?? 'task-1',
2400
- transferToMemberId: entity.transferToMemberId ?? null,
2401
- }),
2402
- ),
2403
- save: jest.fn((entity: TaskDecisionEntity) => {
2404
- savedDecision = entity;
2405
-
2406
- return Promise.resolve(entity);
2407
- }),
2408
- });
2409
- const transactionalActivityRepository = createRepository<ActivityLogEntity>({
2410
- create: jest.fn(
2411
- (entity: Partial<ActivityLogEntity>): ActivityLogEntity =>
2412
- Object.assign(new ActivityLogEntity(), {
2413
- actorMemberId: entity.actorMemberId ?? null,
2414
- createdAt: new Date('2026-05-04T09:00:00.000Z'),
2415
- eventType:
2416
- entity.eventType ??
2417
- ActivityLogEventTypeEnum.ENGINE_PROCESS_REQUESTED,
2418
- id: entity.id ?? 'activity-log',
2419
- instanceId: entity.instanceId ?? 'instance-1',
2420
- nodeId: entity.nodeId ?? null,
2421
- payload: entity.payload ?? {},
2422
- taskId: entity.taskId ?? null,
2423
- }),
2424
- ),
2425
- findOne: jest.fn(() => Promise.resolve(latestReturnActivity ?? null)),
2426
- save: jest.fn(
2427
- (
2428
- entityOrEntities: ActivityLogEntity | ActivityLogEntity[],
2429
- ): Promise<ActivityLogEntity | ActivityLogEntity[]> => {
2430
- if (Array.isArray(entityOrEntities)) {
2431
- savedActivityLogs = [...entityOrEntities];
2432
-
2433
- return Promise.resolve(entityOrEntities);
2434
- }
2435
-
2436
- savedProcessLog = entityOrEntities;
2437
- savedSingleActivityLogs = [
2438
- ...savedSingleActivityLogs,
2439
- entityOrEntities,
2440
- ];
2441
-
2442
- return Promise.resolve(entityOrEntities);
2443
- },
2444
- ),
2445
- });
2446
- const transactionalMembershipRepository = createRepository<MembershipEntity>({
2447
- find: jest.fn(() => Promise.resolve(processMemberships)),
2448
- });
2449
- const transactionalManagerResolutionRepository =
2450
- createRepository<ManagerResolutionEntity>({
2451
- find: jest.fn(() => Promise.resolve(processManagerResolutions)),
2452
- });
2453
- const orgUnitQueryBuilder = {
2454
- andWhere: jest.fn().mockReturnThis(),
2455
- getMany: jest.fn(() => Promise.resolve(processOrgUnits)),
2456
- where: jest.fn().mockReturnThis(),
2457
- };
2458
- const transactionalOrgUnitRepository = createRepository<OrgUnitEntity>({
2459
- createQueryBuilder: jest.fn(() => orgUnitQueryBuilder),
2460
- find: jest.fn(() => Promise.resolve(processOrgUnits)),
2461
- });
2462
- const managerQuery = jest.fn<Promise<unknown>, [string, readonly unknown[]]>(
2463
- () => Promise.resolve([]),
2464
- );
2465
- const transactionManager = {
2466
- getRepository: jest.fn((target: unknown): unknown => {
2467
- if (target === ApprovalInstanceEntity) {
2468
- return transactionalInstanceRepository;
2469
- }
2470
-
2471
- if (target === WorkflowTokenEntity) {
2472
- return transactionalTokenRepository;
2473
- }
2474
-
2475
- if (target === ActivityLogEntity) {
2476
- return transactionalActivityRepository;
2477
- }
2478
-
2479
- if (target === TaskEntity) {
2480
- return transactionalTaskRepository;
2481
- }
2482
-
2483
- if (target === TaskCandidateEntity) {
2484
- return transactionalTaskCandidateRepository;
2485
- }
2486
-
2487
- if (target === TaskDecisionEntity) {
2488
- return transactionalTaskDecisionRepository;
2489
- }
2490
-
2491
- if (target === NotificationEntity) {
2492
- return notificationRepository;
2493
- }
2494
-
2495
- if (target === MembershipEntity) {
2496
- return transactionalMembershipRepository;
2497
- }
2498
-
2499
- if (target === ManagerResolutionEntity) {
2500
- return transactionalManagerResolutionRepository;
2501
- }
2502
-
2503
- if (target === OrgUnitEntity) {
2504
- return transactionalOrgUnitRepository;
2505
- }
2506
-
2507
- if (target === AdhocDirectiveEntity) {
2508
- return transactionalAdhocDirectiveRepository;
2509
- }
2510
-
2511
- return createRepository<ObjectLiteral>({});
2512
- }),
2513
- query: managerQuery,
2514
- };
2515
- const transaction = jest.fn(
2516
- <TResult>(
2517
- callback: (manager: typeof transactionManager) => Promise<TResult>,
2518
- ) => callback(transactionManager),
2519
- );
2520
- Object.assign(instanceRepository, {
2521
- manager: {
2522
- getRepository: jest.fn((target: unknown): unknown => {
2523
- if (target === MembershipEntity) {
2524
- return transactionalMembershipRepository;
2525
- }
2526
-
2527
- return createRepository<ObjectLiteral>({});
2528
- }),
2529
- transaction,
2530
- },
2531
- });
2532
-
2533
- return {
2534
- managerQuery,
2535
- notificationFind,
2536
- rootInstanceCount,
2537
- rootInstanceFind,
2538
- rootInstanceQueryBuilder,
2539
- rootTaskFind,
2540
- get savedAdhocDirectives(): readonly AdhocDirectiveEntity[] {
2541
- return processAdhocDirectiveRows;
2542
- },
2543
- get savedDecision(): TaskDecisionEntity | null {
2544
- return savedDecision;
2545
- },
2546
- get savedActivityLogs(): readonly ActivityLogEntity[] {
2547
- return savedActivityLogs;
2548
- },
2549
- get savedInstance(): ApprovalInstanceEntity | null {
2550
- return savedInstance;
2551
- },
2552
- get savedProcessLog(): ActivityLogEntity | null {
2553
- return savedProcessLog;
2554
- },
2555
- get savedProcessToken(): WorkflowTokenEntity | null {
2556
- return savedProcessToken;
2557
- },
2558
- get savedSingleActivityLogs(): readonly ActivityLogEntity[] {
2559
- return savedSingleActivityLogs;
2560
- },
2561
- get savedTasks(): readonly TaskEntity[] {
2562
- return savedTasks;
2563
- },
2564
- get savedWorkflowTokens(): readonly WorkflowTokenEntity[] {
2565
- return processTokens;
2566
- },
2567
- get savedToken(): WorkflowTokenEntity | null {
2568
- return savedToken;
2569
- },
2570
- service: new WorkflowEngineService(
2571
- instanceRepository,
2572
- tokenRepository,
2573
- taskRepository,
2574
- taskCandidateRepository,
2575
- taskDecisionRepository,
2576
- notificationRepository,
2577
- activityLogRepository,
2578
- adhocDirectiveRepository,
2579
- templateRepository,
2580
- templateVersionRepository,
2581
- formVersionRepository,
2582
- attachmentService as unknown as AttachmentService,
2583
- new ConditionService(),
2584
- delegationService as unknown as DelegationService,
2585
- notificationService as unknown as NotificationService,
2586
- signatureService as unknown as SignatureService,
2587
- serviceTaskDispatcher,
2588
- ),
2589
- notificationService,
2590
- };
2591
- }
2592
-
2593
- function createRepository<TEntity extends ObjectLiteral>(
2594
- value: Readonly<Record<string, unknown>>,
2595
- ): jest.Mocked<Partial<import('typeorm').Repository<TEntity>>> &
2596
- import('typeorm').Repository<TEntity> {
2597
- return value as unknown as jest.Mocked<
2598
- Partial<import('typeorm').Repository<TEntity>>
2599
- > &
2600
- import('typeorm').Repository<TEntity>;
2601
- }
2602
-
2603
- function createApprovalInstanceQueryBuilderMock(
2604
- rootInstanceFind: jest.Mock<
2605
- Promise<readonly ApprovalInstanceEntity[]>,
2606
- [Readonly<Record<string, unknown>>?]
2607
- >,
2608
- rootInstanceCount: jest.Mock<Promise<number>, []>,
2609
- ): ApprovalInstanceQueryBuilderMock {
2610
- const queryBuilder: ApprovalInstanceQueryBuilderMock = {
2611
- andWhere: jest.fn<
2612
- ApprovalInstanceQueryBuilderMock,
2613
- [string, Readonly<Record<string, unknown>>?]
2614
- >((): ApprovalInstanceQueryBuilderMock => queryBuilder),
2615
- getCount: rootInstanceCount,
2616
- getMany: jest.fn(() => rootInstanceFind()),
2617
- orderBy: jest.fn<
2618
- ApprovalInstanceQueryBuilderMock,
2619
- [string, 'ASC' | 'DESC']
2620
- >((): ApprovalInstanceQueryBuilderMock => queryBuilder),
2621
- skip: jest.fn<ApprovalInstanceQueryBuilderMock, [number]>(
2622
- (): ApprovalInstanceQueryBuilderMock => queryBuilder,
2623
- ),
2624
- take: jest.fn<ApprovalInstanceQueryBuilderMock, [number]>(
2625
- (): ApprovalInstanceQueryBuilderMock => queryBuilder,
2626
- ),
2627
- };
2628
-
2629
- return queryBuilder;
2630
- }
2631
-
2632
- function createTemplate(
2633
- currentVersionId: string | null,
2634
- ): ApprovalTemplateEntity {
2635
- return {
2636
- category: null,
2637
- createdAt: new Date('2026-05-04T09:00:00.000Z'),
2638
- createdByMemberId: null,
2639
- currentVersionId,
2640
- deletedAt: null,
2641
- description: null,
2642
- id: 'template-1',
2643
- name: '費用申請',
2644
- updatedAt: new Date('2026-05-04T09:00:00.000Z'),
2645
- };
2646
- }
2647
-
2648
- function createTemplateVersion(
2649
- status: ApprovalTemplateVersionStatusEnum,
2650
- ): ApprovalTemplateVersionEntity {
2651
- return {
2652
- archivedAt: null,
2653
- createdAt: new Date('2026-05-04T09:00:00.000Z'),
2654
- formDefinitionVersionId: 'form-version-1',
2655
- id: 'template-version-1',
2656
- initiatorPolicyCel: null,
2657
- notificationConfig: null,
2658
- notificationConfigJson: null,
2659
- publishedAt: new Date('2026-05-04T09:00:00.000Z'),
2660
- publishedByMemberId: null,
2661
- slaDefaults: null,
2662
- slaDefaultsJson: null,
2663
- status,
2664
- templateId: 'template-1',
2665
- updatedAt: new Date('2026-05-04T09:00:00.000Z'),
2666
- version: 1,
2667
- workflowDefinition: {
2668
- edges: [
2669
- {
2670
- data: {},
2671
- id: 'edge_start_end',
2672
- source: 'start',
2673
- target: 'end',
2674
- type: 'smoothstep',
2675
- },
2676
- ],
2677
- meta: { schemaVersion: 1 },
2678
- nodes: [
2679
- {
2680
- data: { label: '開始' },
2681
- id: 'start',
2682
- position: { x: 80, y: 160 },
2683
- type: 'startEvent',
2684
- },
2685
- {
2686
- data: { endState: 'APPROVED', label: '完成' },
2687
- id: 'end',
2688
- position: { x: 520, y: 160 },
2689
- type: 'endEvent',
2690
- },
2691
- ],
2692
- },
2693
- workflowDefinitionJson: '',
2694
- };
2695
- }
2696
-
2697
- function createFormVersion(
2698
- status: FormDefinitionVersionStatusEnum,
2699
- schema: FormDefinitionSchema = { fields: [], schemaVersion: 1 },
2700
- ): FormDefinitionVersionEntity {
2701
- return {
2702
- archivedAt: null,
2703
- createdAt: new Date('2026-05-04T09:00:00.000Z'),
2704
- formDefinitionId: 'form-1',
2705
- id: 'form-version-1',
2706
- publishedAt: new Date('2026-05-04T09:00:00.000Z'),
2707
- publishedByMemberId: null,
2708
- schema,
2709
- schemaJson: '',
2710
- status,
2711
- uiSchema: {
2712
- layout: [],
2713
- schemaVersion: 1,
2714
- },
2715
- uiSchemaJson: '',
2716
- updatedAt: new Date('2026-05-04T09:00:00.000Z'),
2717
- version: 1,
2718
- };
2719
- }
2720
-
2721
- function createRequiredReasonFormSchema(): FormDefinitionSchema {
2722
- return {
2723
- fields: [
2724
- {
2725
- fieldKey: 'reason',
2726
- label: '事由',
2727
- required: true,
2728
- type: 'text',
2729
- },
2730
- ],
2731
- schemaVersion: 1,
2732
- };
2733
- }
2734
-
2735
- function createConditionalAttachmentFormSchema(): FormDefinitionSchema {
2736
- return {
2737
- fields: [
2738
- {
2739
- fieldKey: 'needsAttachment',
2740
- label: '需要附件',
2741
- required: false,
2742
- type: 'boolean',
2743
- },
2744
- {
2745
- fieldKey: 'attachments',
2746
- label: '附件',
2747
- required: false,
2748
- requiredWhen: 'form.needsAttachment == true',
2749
- type: 'file_upload',
2750
- },
2751
- ],
2752
- schemaVersion: 1,
2753
- };
2754
- }
2755
-
2756
- function createApprovalInstance({
2757
- formDefinitionSnapshot,
2758
- formData,
2759
- id,
2760
- initiatorMemberId,
2761
- state,
2762
- title,
2763
- workflowSnapshot,
2764
- }: {
2765
- readonly formDefinitionSnapshot?: Readonly<Record<string, unknown>>;
2766
- readonly formData?: Readonly<Record<string, unknown>>;
2767
- readonly id?: string;
2768
- readonly initiatorMemberId?: string;
2769
- readonly state?: ApprovalInstanceStateEnum;
2770
- readonly title?: string;
2771
- readonly workflowSnapshot?: WorkflowDefinition;
2772
- } = {}): ApprovalInstanceEntity {
2773
- return Object.assign(new ApprovalInstanceEntity(), {
2774
- completedAt: null,
2775
- createdAt: new Date('2026-05-04T09:00:00.000Z'),
2776
- formData: formData ?? {},
2777
- formDefinitionSnapshot: formDefinitionSnapshot ?? {
2778
- schema: {
2779
- fields: [],
2780
- schemaVersion: 1,
2781
- },
2782
- },
2783
- id: id ?? 'instance-1',
2784
- initiatorMemberId: initiatorMemberId ?? 'member-001',
2785
- initiatorMetadataSnapshot: {},
2786
- startedAt: new Date('2026-05-04T09:00:00.000Z'),
2787
- state: state ?? ApprovalInstanceStateEnum.RUNNING,
2788
- templateId: 'template-1',
2789
- templateVersionId: 'template-version-1',
2790
- title: title ?? '費用申請',
2791
- updatedAt: new Date('2026-05-04T09:00:00.000Z'),
2792
- workflowSnapshot: workflowSnapshot ?? {
2793
- edges: [],
2794
- meta: { schemaVersion: 1 },
2795
- nodes: [],
2796
- },
2797
- });
2798
- }
2799
-
2800
- function createActivityLog(
2801
- value: Partial<ActivityLogEntity>,
2802
- ): ActivityLogEntity {
2803
- return Object.assign(new ActivityLogEntity(), {
2804
- actorMemberId: value.actorMemberId ?? null,
2805
- createdAt: value.createdAt ?? new Date('2026-05-04T09:00:00.000Z'),
2806
- eventType:
2807
- value.eventType ?? ActivityLogEventTypeEnum.ENGINE_PROCESS_REQUESTED,
2808
- id: value.id ?? 'activity-log',
2809
- instanceId: value.instanceId ?? 'instance-1',
2810
- nodeId: value.nodeId ?? null,
2811
- payload: value.payload ?? {},
2812
- taskId: value.taskId ?? null,
2813
- });
2814
- }
2815
-
2816
- function compareTokenCreatedAt(
2817
- left: WorkflowTokenEntity,
2818
- right: WorkflowTokenEntity,
2819
- ): number {
2820
- return left.createdAt.getTime() - right.createdAt.getTime();
2821
- }
2822
-
2823
- function createWorkflowToken(
2824
- value: Partial<WorkflowTokenEntity>,
2825
- ): WorkflowTokenEntity {
2826
- return {
2827
- consumedAt: value.consumedAt ?? null,
2828
- createdAt: value.createdAt ?? new Date('2026-05-04T09:00:00.000Z'),
2829
- currentNodeId: value.currentNodeId ?? 'start',
2830
- id: value.id ?? 'token-1',
2831
- instanceId: value.instanceId ?? 'instance-1',
2832
- parentTokenId: value.parentTokenId ?? null,
2833
- status: value.status ?? WorkflowTokenStatusEnum.ACTIVE,
2834
- };
2835
- }
2836
-
2837
- function createTask(value: Partial<TaskEntity>): TaskEntity {
2838
- return Object.assign(new TaskEntity(), {
2839
- adhocDirectiveId: value.adhocDirectiveId ?? null,
2840
- adhocOriginTaskId: value.adhocOriginTaskId ?? null,
2841
- adhocType: value.adhocType ?? null,
2842
- assigneeMemberId: value.assigneeMemberId ?? 'member-finance',
2843
- completedAt: value.completedAt ?? null,
2844
- createdAt: value.createdAt ?? new Date('2026-05-04T09:00:00.000Z'),
2845
- delegationChain: value.delegationChain ?? [],
2846
- id: value.id ?? 'task-1',
2847
- instanceId: value.instanceId ?? 'instance-1',
2848
- isAdhoc: value.isAdhoc ?? false,
2849
- nodeId: value.nodeId ?? 'task_finance',
2850
- openedAt: value.openedAt ?? null,
2851
- originalAssigneeMemberId:
2852
- value.originalAssigneeMemberId ?? 'member-finance',
2853
- slaDueAt: value.slaDueAt ?? null,
2854
- status: value.status ?? TaskStatusEnum.PENDING,
2855
- tokenId: value.tokenId ?? 'token-1',
2856
- });
2857
- }
2858
-
2859
- function createAdhocDirective(
2860
- value: Partial<AdhocDirectiveEntity>,
2861
- ): AdhocDirectiveEntity {
2862
- return Object.assign(new AdhocDirectiveEntity(), {
2863
- channels: value.channels ?? null,
2864
- comment: value.comment ?? null,
2865
- consumedAt: value.consumedAt ?? null,
2866
- createdAt: value.createdAt ?? new Date('2026-05-04T09:00:00.000Z'),
2867
- createdByMemberId: value.createdByMemberId ?? 'member-finance',
2868
- id: value.id ?? 'directive-seed-1',
2869
- instanceId: value.instanceId ?? 'instance-1',
2870
- onReject: value.onReject ?? null,
2871
- originNodeId: value.originNodeId ?? 'task_finance',
2872
- originTaskId: value.originTaskId ?? 'task-1',
2873
- status: value.status ?? AdhocDirectiveStatusEnum.PENDING,
2874
- targetKind: value.targetKind ?? AdhocTargetKindEnum.MEMBER,
2875
- targetValue: value.targetValue ?? {
2876
- kind: AdhocTargetKindEnum.MEMBER,
2877
- memberIds: ['member-d'],
2878
- },
2879
- type: value.type ?? AdhocDirectiveTypeEnum.COUNTERSIGN,
2880
- });
2881
- }
2882
-
2883
- function createAuthContext(memberId: string): BPMAuthContext {
2884
- return {
2885
- memberId,
2886
- metadata: {},
2887
- permissions: ['instance.read'],
2888
- roles: ['REQUESTER'],
2889
- };
2890
- }
2891
-
2892
- function createMembership(value: Partial<MembershipEntity>): MembershipEntity {
2893
- return Object.assign(new MembershipEntity(), {
2894
- createdAt: new Date('2026-05-04T09:00:00.000Z'),
2895
- effectiveFrom: value.effectiveFrom ?? '2026-01-01',
2896
- effectiveTo: value.effectiveTo ?? null,
2897
- id: value.id ?? 'membership-1',
2898
- isPrimary: value.isPrimary ?? false,
2899
- memberId: value.memberId ?? 'member-001',
2900
- orgUnitId: value.orgUnitId ?? 'org-finance',
2901
- positionId: value.positionId ?? null,
2902
- updatedAt: new Date('2026-05-04T09:00:00.000Z'),
2903
- });
2904
- }
2905
-
2906
- function createManagerResolution(
2907
- value: Partial<ManagerResolutionEntity>,
2908
- ): ManagerResolutionEntity {
2909
- return Object.assign(new ManagerResolutionEntity(), {
2910
- createdAt: new Date('2026-05-04T09:00:00.000Z'),
2911
- effectiveFrom: value.effectiveFrom ?? '2026-01-01',
2912
- effectiveTo: value.effectiveTo ?? null,
2913
- id: value.id ?? 'manager-resolution-1',
2914
- managerMemberId: value.managerMemberId ?? 'member-manager',
2915
- priority: value.priority ?? 0,
2916
- scopeId: value.scopeId ?? 'org-finance',
2917
- scopeType: value.scopeType ?? ManagerResolutionScopeTypeEnum.ORG_UNIT,
2918
- });
2919
- }
2920
-
2921
- function createOrgUnit(value: Partial<OrgUnitEntity>): OrgUnitEntity {
2922
- return Object.assign(new OrgUnitEntity(), {
2923
- code: value.code ?? 'FIN',
2924
- createdAt: new Date('2026-05-04T09:00:00.000Z'),
2925
- deletedAt: value.deletedAt ?? null,
2926
- id: value.id ?? 'org-finance',
2927
- metadata: value.metadata ?? {},
2928
- name: value.name ?? '財務部',
2929
- parentId: value.parentId ?? null,
2930
- path: value.path ?? 'org.finance',
2931
- type: value.type ?? OrgUnitTypeEnum.DEPARTMENT,
2932
- updatedAt: new Date('2026-05-04T09:00:00.000Z'),
2933
- });
2934
- }
2935
-
2936
- function createLinearUserTaskWorkflow({
2937
- allowAddSigner = false,
2938
- approverResolver = {
2939
- memberIds: ['member-finance'],
2940
- type: 'DIRECT',
2941
- },
2942
- slaDuration = null,
2943
- }: {
2944
- readonly allowAddSigner?: boolean;
2945
- readonly approverResolver?: ApproverResolver;
2946
- readonly slaDuration?: string | null;
2947
- } = {}): WorkflowDefinition {
2948
- return {
2949
- edges: [
2950
- {
2951
- data: {},
2952
- id: 'edge_start_task',
2953
- source: 'start',
2954
- target: 'task_finance',
2955
- type: 'smoothstep',
2956
- },
2957
- {
2958
- data: {},
2959
- id: 'edge_task_end',
2960
- source: 'task_finance',
2961
- target: 'end',
2962
- type: 'smoothstep',
2963
- },
2964
- ],
2965
- meta: { schemaVersion: 1 },
2966
- nodes: [
2967
- {
2968
- data: { label: '開始' },
2969
- id: 'start',
2970
- position: { x: 80, y: 160 },
2971
- type: 'startEvent',
2972
- },
2973
- {
2974
- data: {
2975
- allowAddSigner,
2976
- allowReject: true,
2977
- allowTransfer: false,
2978
- approverResolver,
2979
- decisionPolicy: { type: 'SINGLE' },
2980
- label: '財務簽核',
2981
- returnBehavior: {
2982
- allowReturn: false,
2983
- allowedTargets: 'PREVIOUS',
2984
- },
2985
- ...(slaDuration
2986
- ? {
2987
- sla: {
2988
- duration: slaDuration,
2989
- onTimeout: 'REMIND' as const,
2990
- warningAt: 0.5,
2991
- },
2992
- }
2993
- : {}),
2994
- },
2995
- id: 'task_finance',
2996
- position: { x: 300, y: 160 },
2997
- type: 'userTask',
2998
- },
2999
- {
3000
- data: { endState: 'APPROVED', label: '完成' },
3001
- id: 'end',
3002
- position: { x: 520, y: 160 },
3003
- type: 'endEvent',
3004
- },
3005
- ],
3006
- };
3007
- }
3008
-
3009
- function createNotifyServiceTaskWorkflow(): WorkflowDefinition {
3010
- return {
3011
- edges: [
3012
- {
3013
- data: {},
3014
- id: 'edge_start_notify',
3015
- source: 'start',
3016
- target: 'notify_finance',
3017
- type: 'smoothstep',
3018
- },
3019
- ],
3020
- meta: { schemaVersion: 1 },
3021
- nodes: [
3022
- {
3023
- data: { label: '開始' },
3024
- id: 'start',
3025
- position: { x: 80, y: 160 },
3026
- type: 'startEvent',
3027
- },
3028
- {
3029
- data: {
3030
- action: {
3031
- channels: ['IN_APP'],
3032
- recipients: {
3033
- memberIds: ['member-finance', 'member-admin'],
3034
- type: 'DIRECT',
3035
- },
3036
- template: '請留意案件 {{instanceTitle}}。',
3037
- type: 'NOTIFY',
3038
- },
3039
- label: '財務知會',
3040
- },
3041
- id: 'notify_finance',
3042
- position: { x: 300, y: 160 },
3043
- type: 'serviceTask',
3044
- },
3045
- ],
3046
- };
3047
- }
3048
-
3049
- function createWebhookServiceTaskWorkflow(): WorkflowDefinition {
3050
- return {
3051
- edges: [
3052
- {
3053
- data: {},
3054
- id: 'edge_start_webhook',
3055
- source: 'start',
3056
- target: 'webhook_erp',
3057
- type: 'smoothstep',
3058
- },
3059
- {
3060
- data: {},
3061
- id: 'edge_webhook_end',
3062
- source: 'webhook_erp',
3063
- target: 'end',
3064
- type: 'smoothstep',
3065
- },
3066
- ],
3067
- meta: { schemaVersion: 1 },
3068
- nodes: [
3069
- {
3070
- data: { label: '開始' },
3071
- id: 'start',
3072
- position: { x: 80, y: 160 },
3073
- type: 'startEvent',
3074
- },
3075
- {
3076
- data: {
3077
- action: {
3078
- payload: '{ title: instance.title, amount: form.amount }',
3079
- type: 'WEBHOOK',
3080
- url: 'https://example.test/workflow-hook',
3081
- },
3082
- label: '同步 ERP',
3083
- },
3084
- id: 'webhook_erp',
3085
- position: { x: 300, y: 160 },
3086
- type: 'serviceTask',
3087
- },
3088
- {
3089
- data: { endState: 'APPROVED', label: '完成' },
3090
- id: 'end',
3091
- position: { x: 520, y: 160 },
3092
- type: 'endEvent',
3093
- },
3094
- ],
3095
- };
3096
- }
3097
-
3098
- function createSetFormFieldServiceTaskWorkflow(): WorkflowDefinition {
3099
- return {
3100
- edges: [
3101
- {
3102
- data: {},
3103
- id: 'edge_start_set_field',
3104
- source: 'start',
3105
- target: 'set_approval_level',
3106
- type: 'smoothstep',
3107
- },
3108
- {
3109
- data: {},
3110
- id: 'edge_set_field_end',
3111
- source: 'set_approval_level',
3112
- target: 'end',
3113
- type: 'smoothstep',
3114
- },
3115
- ],
3116
- meta: { schemaVersion: 1 },
3117
- nodes: [
3118
- {
3119
- data: { label: '開始' },
3120
- id: 'start',
3121
- position: { x: 80, y: 160 },
3122
- type: 'startEvent',
3123
- },
3124
- {
3125
- data: {
3126
- action: {
3127
- fieldPath: 'form.approvalLevel',
3128
- type: 'SET_FORM_FIELD',
3129
- value: '"主管簽核"',
3130
- },
3131
- label: '設定簽核層級',
3132
- },
3133
- id: 'set_approval_level',
3134
- position: { x: 300, y: 160 },
3135
- type: 'serviceTask',
3136
- },
3137
- {
3138
- data: { endState: 'APPROVED', label: '完成' },
3139
- id: 'end',
3140
- position: { x: 520, y: 160 },
3141
- type: 'endEvent',
3142
- },
3143
- ],
3144
- };
3145
- }
3146
-
3147
- function createExclusiveGatewayWorkflow({
3148
- highCondition = 'form.amount > 1000',
3149
- includeStructuredCondition = true,
3150
- }: {
3151
- readonly highCondition?: string;
3152
- readonly includeStructuredCondition?: boolean;
3153
- } = {}): WorkflowDefinition {
3154
- return {
3155
- edges: [
3156
- {
3157
- data: {},
3158
- id: 'edge_start_gateway',
3159
- source: 'start',
3160
- target: 'gateway_amount',
3161
- type: 'smoothstep',
3162
- },
3163
- {
3164
- data: {
3165
- condition: highCondition,
3166
- ...(includeStructuredCondition
3167
- ? {
3168
- conditionFieldKey: 'amount',
3169
- conditionOperator: 'GREATER_THAN' as const,
3170
- conditionValue: '1000',
3171
- }
3172
- : {}),
3173
- label: '金額大於 1000',
3174
- },
3175
- id: 'edge_gateway_high',
3176
- source: 'gateway_amount',
3177
- target: 'task_high',
3178
- type: 'smoothstep',
3179
- },
3180
- {
3181
- data: { isDefault: true, label: '其他情況' },
3182
- id: 'edge_gateway_default',
3183
- source: 'gateway_amount',
3184
- target: 'task_default',
3185
- type: 'smoothstep',
3186
- },
3187
- {
3188
- data: {},
3189
- id: 'edge_high_end',
3190
- source: 'task_high',
3191
- target: 'end',
3192
- type: 'smoothstep',
3193
- },
3194
- {
3195
- data: {},
3196
- id: 'edge_default_end',
3197
- source: 'task_default',
3198
- target: 'end',
3199
- type: 'smoothstep',
3200
- },
3201
- ],
3202
- meta: { schemaVersion: 1 },
3203
- nodes: [
3204
- {
3205
- data: { label: '開始' },
3206
- id: 'start',
3207
- position: { x: 80, y: 160 },
3208
- type: 'startEvent',
3209
- },
3210
- {
3211
- data: { direction: 'split', label: '金額分流', triggerMode: 'AND' },
3212
- id: 'gateway_amount',
3213
- position: { x: 260, y: 160 },
3214
- type: 'exclusiveGateway',
3215
- },
3216
- createUserTaskNode('task_high', '高額簽核', 'member-high', 440, 80),
3217
- createUserTaskNode(
3218
- 'task_default',
3219
- '一般簽核',
3220
- 'member-default',
3221
- 440,
3222
- 240,
3223
- ),
3224
- {
3225
- data: { endState: 'APPROVED', label: '完成' },
3226
- id: 'end',
3227
- position: { x: 660, y: 160 },
3228
- type: 'endEvent',
3229
- },
3230
- ],
3231
- };
3232
- }
3233
-
3234
- function createParallelApprovalWorkflow(
3235
- finalTriggerMode: 'AND' | 'OR',
3236
- ): WorkflowDefinition {
3237
- return {
3238
- edges: [
3239
- {
3240
- data: {},
3241
- id: 'edge_start_a',
3242
- source: 'start',
3243
- target: 'task_a',
3244
- type: 'smoothstep',
3245
- },
3246
- {
3247
- data: {},
3248
- id: 'edge_start_b',
3249
- source: 'start',
3250
- target: 'task_b',
3251
- type: 'smoothstep',
3252
- },
3253
- {
3254
- data: {},
3255
- id: 'edge_a_final',
3256
- source: 'task_a',
3257
- target: 'task_final',
3258
- type: 'smoothstep',
3259
- },
3260
- {
3261
- data: {},
3262
- id: 'edge_b_final',
3263
- source: 'task_b',
3264
- target: 'task_final',
3265
- type: 'smoothstep',
3266
- },
3267
- {
3268
- data: {},
3269
- id: 'edge_final_end',
3270
- source: 'task_final',
3271
- target: 'end',
3272
- type: 'smoothstep',
3273
- },
3274
- ],
3275
- meta: { schemaVersion: 1 },
3276
- nodes: [
3277
- {
3278
- data: { label: '開始' },
3279
- id: 'start',
3280
- position: { x: 80, y: 160 },
3281
- type: 'startEvent',
3282
- },
3283
- createUserTaskNode('task_a', 'A 簽核', 'member-a', 300, 80),
3284
- createUserTaskNode('task_b', 'B 簽核', 'member-b', 300, 240),
3285
- createUserTaskNode(
3286
- 'task_final',
3287
- '彙整簽核',
3288
- 'member-final',
3289
- 520,
3290
- 160,
3291
- finalTriggerMode,
3292
- ),
3293
- {
3294
- data: { endState: 'APPROVED', label: '完成' },
3295
- id: 'end',
3296
- position: { x: 740, y: 160 },
3297
- type: 'endEvent',
3298
- },
3299
- ],
3300
- };
3301
- }
3302
-
3303
- function createUserTaskNode(
3304
- id: string,
3305
- label: string,
3306
- memberId: string,
3307
- x: number,
3308
- y: number,
3309
- triggerMode: 'AND' | 'OR' = 'AND',
3310
- ): WorkflowDefinition['nodes'][number] {
3311
- return {
3312
- data: {
3313
- allowAddSigner: false,
3314
- allowReject: true,
3315
- allowTransfer: false,
3316
- approverResolver: {
3317
- memberIds: [memberId],
3318
- type: 'DIRECT',
3319
- },
3320
- decisionPolicy: { type: 'SINGLE' },
3321
- label,
3322
- returnBehavior: {
3323
- allowReturn: false,
3324
- allowedTargets: 'PREVIOUS',
3325
- },
3326
- triggerMode,
3327
- },
3328
- id,
3329
- position: { x, y },
3330
- type: 'userTask',
3331
- };
3332
- }
3333
-
3334
- function readTaskByNodeId(
3335
- tasks: readonly TaskEntity[],
3336
- nodeId: string,
3337
- ): TaskEntity {
3338
- const task = tasks.find((candidate) => candidate.nodeId === nodeId);
3339
-
3340
- if (!task) {
3341
- throw new Error(`Task for node ${nodeId} was not found`);
3342
- }
3343
-
3344
- return task;
3345
- }