@rytass/bpm-core-nestjs-module 0.1.3 → 0.1.5

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