@things-factory/worklist 9.1.19 → 10.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist-client/components/activity-thread-timeline.d.ts +1 -9
  2. package/dist-client/components/activity-thread-timeline.js +1 -3
  3. package/dist-client/components/activity-thread-timeline.js.map +1 -1
  4. package/dist-client/pages/activity/activity-list-page.d.ts +1 -7
  5. package/dist-client/pages/activity/activity-list-page.js +125 -234
  6. package/dist-client/pages/activity/activity-list-page.js.map +1 -1
  7. package/dist-client/pages/activity/activity-page.d.ts +1 -7
  8. package/dist-client/pages/activity/activity-page.js +51 -93
  9. package/dist-client/pages/activity/activity-page.js.map +1 -1
  10. package/dist-client/pages/activity/starter-list-page.d.ts +1 -7
  11. package/dist-client/pages/activity/starter-list-page.js +33 -62
  12. package/dist-client/pages/activity/starter-list-page.js.map +1 -1
  13. package/dist-client/pages/activity-approval/activity-approval-list-page.d.ts +1 -7
  14. package/dist-client/pages/activity-approval/activity-approval-list-page.js +50 -95
  15. package/dist-client/pages/activity-approval/activity-approval-list-page.js.map +1 -1
  16. package/dist-client/pages/activity-approval/activity-approval-page.d.ts +1 -7
  17. package/dist-client/pages/activity-approval/activity-approval-page.js +73 -119
  18. package/dist-client/pages/activity-approval/activity-approval-page.js.map +1 -1
  19. package/dist-client/pages/activity-instance/activity-instance-list-page.d.ts +0 -6
  20. package/dist-client/pages/activity-instance/activity-instance-list-page.js +63 -120
  21. package/dist-client/pages/activity-instance/activity-instance-list-page.js.map +1 -1
  22. package/dist-client/pages/activity-instance/activity-instance-search-page.d.ts +1 -7
  23. package/dist-client/pages/activity-instance/activity-instance-search-page.js +55 -101
  24. package/dist-client/pages/activity-instance/activity-instance-search-page.js.map +1 -1
  25. package/dist-client/pages/activity-instance/activity-instance-start-page.d.ts +1 -7
  26. package/dist-client/pages/activity-instance/activity-instance-start-page.js +65 -109
  27. package/dist-client/pages/activity-instance/activity-instance-start-page.js.map +1 -1
  28. package/dist-client/pages/activity-stats/activity-stats-list-page.d.ts +1 -7
  29. package/dist-client/pages/activity-stats/activity-stats-list-page.js +50 -95
  30. package/dist-client/pages/activity-stats/activity-stats-list-page.js.map +1 -1
  31. package/dist-client/pages/activity-store/activity-store-page.d.ts +1 -7
  32. package/dist-client/pages/activity-store/activity-store-page.js +2 -3
  33. package/dist-client/pages/activity-store/activity-store-page.js.map +1 -1
  34. package/dist-client/pages/activity-supervisor/reporter-list-page.d.ts +1 -7
  35. package/dist-client/pages/activity-supervisor/reporter-list-page.js +36 -66
  36. package/dist-client/pages/activity-supervisor/reporter-list-page.js.map +1 -1
  37. package/dist-client/pages/activity-template/activity-template-list-page.d.ts +1 -7
  38. package/dist-client/pages/activity-template/activity-template-list-page.js +70 -134
  39. package/dist-client/pages/activity-template/activity-template-list-page.js.map +1 -1
  40. package/dist-client/pages/activity-thread/activity-thread-list-page.d.ts +1 -7
  41. package/dist-client/pages/activity-thread/activity-thread-list-page.js +49 -93
  42. package/dist-client/pages/activity-thread/activity-thread-list-page.js.map +1 -1
  43. package/dist-client/pages/activity-thread/activity-thread-page.d.ts +1 -7
  44. package/dist-client/pages/activity-thread/activity-thread-page.js +80 -135
  45. package/dist-client/pages/activity-thread/activity-thread-page.js.map +1 -1
  46. package/dist-client/pages/activity-thread/activity-thread-view-page.d.ts +1 -7
  47. package/dist-client/pages/activity-thread/activity-thread-view-page.js +54 -80
  48. package/dist-client/pages/activity-thread/activity-thread-view-page.js.map +1 -1
  49. package/dist-client/pages/activity-thread/activity-thread-view.js +4 -0
  50. package/dist-client/pages/activity-thread/activity-thread-view.js.map +1 -1
  51. package/dist-client/pages/dashboard/dashboard-home.js +3 -5
  52. package/dist-client/pages/dashboard/dashboard-home.js.map +1 -1
  53. package/dist-client/pages/installable-activity/installable-activity-list-page.d.ts +0 -6
  54. package/dist-client/pages/installable-activity/installable-activity-list-page.js +68 -130
  55. package/dist-client/pages/installable-activity/installable-activity-list-page.js.map +1 -1
  56. package/dist-client/pages/todo/approval-done-list-page.d.ts +1 -7
  57. package/dist-client/pages/todo/approval-done-list-page.js +53 -100
  58. package/dist-client/pages/todo/approval-done-list-page.js.map +1 -1
  59. package/dist-client/pages/todo/approval-pending-list-page.d.ts +0 -6
  60. package/dist-client/pages/todo/approval-pending-list-page.js +63 -119
  61. package/dist-client/pages/todo/approval-pending-list-page.js.map +1 -1
  62. package/dist-client/pages/todo/done-list-calendar-page.d.ts +1 -7
  63. package/dist-client/pages/todo/done-list-calendar-page.js +2 -3
  64. package/dist-client/pages/todo/done-list-calendar-page.js.map +1 -1
  65. package/dist-client/pages/todo/done-list-page.d.ts +1 -7
  66. package/dist-client/pages/todo/done-list-page.js +56 -106
  67. package/dist-client/pages/todo/done-list-page.js.map +1 -1
  68. package/dist-client/pages/todo/draft-list-page.d.ts +1 -7
  69. package/dist-client/pages/todo/draft-list-page.js +49 -88
  70. package/dist-client/pages/todo/draft-list-page.js.map +1 -1
  71. package/dist-client/pages/todo/pickable-list-page.d.ts +1 -7
  72. package/dist-client/pages/todo/pickable-list-page.js +48 -91
  73. package/dist-client/pages/todo/pickable-list-page.js.map +1 -1
  74. package/dist-client/pages/todo/todo-list-page.d.ts +0 -6
  75. package/dist-client/pages/todo/todo-list-page.js +56 -106
  76. package/dist-client/pages/todo/todo-list-page.js.map +1 -1
  77. package/dist-client/pages/worklist-home.js +2 -3
  78. package/dist-client/pages/worklist-home.js.map +1 -1
  79. package/dist-client/route.d.ts +1 -1
  80. package/dist-client/templates/activity-approval-context-template.js +8 -12
  81. package/dist-client/templates/activity-approval-context-template.js.map +1 -1
  82. package/dist-client/templates/activity-instance-context-template.js +8 -12
  83. package/dist-client/templates/activity-instance-context-template.js.map +1 -1
  84. package/dist-client/templates/activity-thread-context-template.js +8 -12
  85. package/dist-client/templates/activity-thread-context-template.js.map +1 -1
  86. package/dist-client/tsconfig.tsbuildinfo +1 -1
  87. package/dist-server/controllers/activity-approval/approve.js +2 -2
  88. package/dist-server/controllers/activity-approval/approve.js.map +1 -1
  89. package/dist-server/controllers/activity-thread/submit.js +2 -2
  90. package/dist-server/controllers/activity-thread/submit.js.map +1 -1
  91. package/dist-server/service/index.d.ts +2 -2
  92. package/dist-server/tsconfig.tsbuildinfo +1 -1
  93. package/package.json +16 -16
  94. package/spec/integration/approval-mixed-types.spec.ts +491 -0
  95. package/spec/integration/approval-role-based.spec.ts +389 -0
  96. package/spec/integration/instance-lifecycle.spec.ts +406 -0
  97. package/spec/integration/role-approval-edge-cases.spec.ts +581 -0
  98. package/spec/unit/controllers/activity-instance-issue.spec.ts +360 -0
  99. package/spec/unit/controllers/activity-thread-submit.spec.ts +384 -0
  100. package/spec/unit/role-approval-escalate-logic.spec.ts +499 -0
  101. package/spec/unit/role-approval-submit-logic.spec.ts +481 -0
  102. package/spec/unit/thread-state-helpers.spec.ts +253 -0
  103. package/translations/en.json +1 -1
@@ -0,0 +1,389 @@
1
+ /**
2
+ * Role-Based Approval Workflow Integration Tests
3
+ * 역할 기반 승인 워크플로우 통합 테스트
4
+ */
5
+
6
+ import { TestDatabase } from '../../../../test/test-database'
7
+ import { withTestTransaction } from '../../../../test/test-context'
8
+ import {
9
+ domainFactory,
10
+ userFactory,
11
+ roleFactory,
12
+ activityFactory,
13
+ activityInstanceFactory,
14
+ activityThreadFactory,
15
+ activityApprovalFactory
16
+ } from '../../../../test/factories'
17
+
18
+ import {
19
+ ActivityInstanceStatus,
20
+ ActivityThreadStatus,
21
+ ActivityApprovalJudgment
22
+ } from '../../../../test/entities/schemas'
23
+
24
+ import {
25
+ createRoleApprovalLineItem,
26
+ simulateApproval,
27
+ simulateRejection,
28
+ simulateEscalation
29
+ } from '../../../../test/helpers/workflow-helpers'
30
+
31
+ describe('Role-Based Approval Workflow Integration Tests', () => {
32
+ let testDb: TestDatabase
33
+
34
+ beforeAll(async () => {
35
+ testDb = TestDatabase.getInstance()
36
+ })
37
+
38
+ describe('ROLE 타입 Approval Line 설정', () => {
39
+ it('approvalLine에 Role 타입이 설정되어야 한다', async () => {
40
+ await withTestTransaction(async (context) => {
41
+ const { tx } = context.state
42
+ const domain = await domainFactory.create({}, tx)
43
+ const approverRole = await roleFactory.create({ name: 'Approver Role', domain }, tx)
44
+
45
+ // Given: Role 타입 approval line
46
+ const approvalLine = [createRoleApprovalLineItem(approverRole)]
47
+
48
+ // When: ActivityInstance 생성
49
+ const instance = await activityInstanceFactory.createWithApprovalLine(
50
+ approvalLine,
51
+ { state: ActivityInstanceStatus.Issued },
52
+ undefined,
53
+ domain,
54
+ tx
55
+ )
56
+
57
+ // Then
58
+ expect(instance.approvalLine).toBeDefined()
59
+ expect(instance.approvalLine?.length).toBe(1)
60
+ expect(instance.approvalLine?.[0].type).toBe('Role')
61
+ expect(instance.approvalLine?.[0].value).toBe(approverRole.id)
62
+ })
63
+ })
64
+
65
+ it('Role 타입 ActivityApproval 생성 시 approverRole이 설정되어야 한다', async () => {
66
+ await withTestTransaction(async (context) => {
67
+ const { tx } = context.state
68
+ const domain = await domainFactory.create({}, tx)
69
+ const approverRole = await roleFactory.create({ name: 'Approver Role', domain }, tx)
70
+
71
+ // When: Role 기반 approval 생성
72
+ const approval = await activityApprovalFactory.createWithRole(
73
+ { judgment: ActivityApprovalJudgment.Pending },
74
+ undefined,
75
+ approverRole,
76
+ tx
77
+ )
78
+
79
+ // Then
80
+ expect(approval.approverRole?.id).toBe(approverRole.id)
81
+ expect(approval.approver).toBeUndefined()
82
+ })
83
+ })
84
+
85
+ it('Role 타입 approval 생성 시 approver는 설정되지 않아야 한다', async () => {
86
+ await withTestTransaction(async (context) => {
87
+ const { tx } = context.state
88
+ const domain = await domainFactory.create({}, tx)
89
+ const approverRole = await roleFactory.create({ name: 'Approver', domain }, tx)
90
+
91
+ // When
92
+ const approval = await activityApprovalFactory.createWithRole(
93
+ {},
94
+ undefined,
95
+ approverRole,
96
+ tx
97
+ )
98
+
99
+ // Then: approver는 undefined, approverRole만 설정됨
100
+ expect(approval.approver).toBeUndefined()
101
+ expect(approval.approverRole).toBeDefined()
102
+ expect(approval.approverRole?.id).toBe(approverRole.id)
103
+ })
104
+ })
105
+ })
106
+
107
+ describe('Role 기반 승인 권한 검증', () => {
108
+ it('해당 Role을 가진 사용자가 승인할 수 있어야 한다', async () => {
109
+ await withTestTransaction(async (context) => {
110
+ const { tx } = context.state
111
+ const domain = await domainFactory.create({}, tx)
112
+ const approverRole = await roleFactory.create({ name: 'Approver', domain }, tx)
113
+
114
+ // Given: Role을 가진 사용자
115
+ const userWithRole = await userFactory.createWithRole({}, approverRole, tx)
116
+
117
+ // Given: Role 기반 approval
118
+ const approval = await activityApprovalFactory.createWithRole(
119
+ { judgment: ActivityApprovalJudgment.Pending },
120
+ undefined,
121
+ approverRole,
122
+ tx
123
+ )
124
+
125
+ // When: 승인 시뮬레이션
126
+ const approved = await simulateApproval(
127
+ approval,
128
+ userWithRole,
129
+ 'Approved by role member',
130
+ { tx, domain, user: userWithRole }
131
+ )
132
+
133
+ // Then
134
+ expect(approved.judgment).toBe(ActivityApprovalJudgment.Approved)
135
+ expect(approved.approver?.id).toBe(userWithRole.id)
136
+ })
137
+ })
138
+
139
+ it('첫 번째 승인자가 Role 기반 approval에 자동 할당되어야 한다', async () => {
140
+ await withTestTransaction(async (context) => {
141
+ const { tx } = context.state
142
+ const domain = await domainFactory.create({}, tx)
143
+ const approverRole = await roleFactory.create({ name: 'Approver', domain }, tx)
144
+ const userWithRole = await userFactory.createWithRole({}, approverRole, tx)
145
+
146
+ // Given: approver가 없는 Role 기반 approval
147
+ const approval = await activityApprovalFactory.createWithRole(
148
+ { judgment: ActivityApprovalJudgment.Pending },
149
+ undefined,
150
+ approverRole,
151
+ tx
152
+ )
153
+ expect(approval.approver).toBeUndefined()
154
+
155
+ // When: 승인 실행 시
156
+ const approved = await simulateApproval(
157
+ approval,
158
+ userWithRole,
159
+ undefined,
160
+ { tx, domain, user: userWithRole }
161
+ )
162
+
163
+ // Then: 승인한 사용자가 approver로 할당됨
164
+ expect(approved.approver?.id).toBe(userWithRole.id)
165
+ })
166
+ })
167
+ })
168
+
169
+ describe('다단계 Role 기반 승인', () => {
170
+ it('여러 레벨의 Role 기반 approval line이 처리되어야 한다', async () => {
171
+ await withTestTransaction(async (context) => {
172
+ const { tx } = context.state
173
+ const domain = await domainFactory.create({}, tx)
174
+
175
+ // Given: 2개의 approver roles
176
+ const firstApproverRole = await roleFactory.create({ name: 'First Approver', domain }, tx)
177
+ const secondApproverRole = await roleFactory.create({ name: 'Second Approver', domain }, tx)
178
+
179
+ const firstApprover = await userFactory.createWithRole({}, firstApproverRole, tx)
180
+ const secondApprover = await userFactory.createWithRole({}, secondApproverRole, tx)
181
+
182
+ // Given: 2단계 approval line
183
+ const approvalLine = [
184
+ createRoleApprovalLineItem(firstApproverRole),
185
+ createRoleApprovalLineItem(secondApproverRole)
186
+ ]
187
+
188
+ const instance = await activityInstanceFactory.createWithApprovalLine(
189
+ approvalLine,
190
+ { state: ActivityInstanceStatus.Started },
191
+ undefined,
192
+ domain,
193
+ tx
194
+ )
195
+
196
+ // When: 첫 번째 레벨 approval 생성 및 승인
197
+ const firstApproval = await activityApprovalFactory.createWithRole(
198
+ { order: 1, judgment: ActivityApprovalJudgment.Pending },
199
+ undefined,
200
+ firstApproverRole,
201
+ tx
202
+ )
203
+
204
+ const escalated = await simulateEscalation(
205
+ firstApproval,
206
+ firstApprover,
207
+ 'Escalating to next level',
208
+ { tx, domain, user: firstApprover }
209
+ )
210
+
211
+ // Then: 에스컬레이션됨
212
+ expect(escalated.judgment).toBe(ActivityApprovalJudgment.Escalated)
213
+
214
+ // When: 두 번째 레벨 approval 생성 및 승인
215
+ const secondApproval = await activityApprovalFactory.createWithRole(
216
+ { order: 2, judgment: ActivityApprovalJudgment.Pending },
217
+ undefined,
218
+ secondApproverRole,
219
+ tx
220
+ )
221
+
222
+ const approved = await simulateApproval(
223
+ secondApproval,
224
+ secondApprover,
225
+ 'Final approval',
226
+ { tx, domain, user: secondApprover }
227
+ )
228
+
229
+ // Then: 최종 승인
230
+ expect(approved.judgment).toBe(ActivityApprovalJudgment.Approved)
231
+ expect(approved.approver?.id).toBe(secondApprover.id)
232
+ })
233
+ })
234
+
235
+ it('Role 기반 승인에서 어느 레벨에서든 반려할 수 있어야 한다', async () => {
236
+ await withTestTransaction(async (context) => {
237
+ const { tx } = context.state
238
+ const domain = await domainFactory.create({}, tx)
239
+ const approverRole = await roleFactory.create({ name: 'Approver', domain }, tx)
240
+ const approver = await userFactory.createWithRole({}, approverRole, tx)
241
+
242
+ // Given: Role 기반 approval
243
+ const approval = await activityApprovalFactory.createWithRole(
244
+ { order: 1, judgment: ActivityApprovalJudgment.Pending },
245
+ undefined,
246
+ approverRole,
247
+ tx
248
+ )
249
+
250
+ // When: 반려
251
+ const rejected = await simulateRejection(
252
+ approval,
253
+ approver,
254
+ 'Needs revision',
255
+ { tx, domain, user: approver }
256
+ )
257
+
258
+ // Then
259
+ expect(rejected.judgment).toBe(ActivityApprovalJudgment.Rejected)
260
+ expect(rejected.comment).toBe('Needs revision')
261
+ })
262
+ })
263
+ })
264
+
265
+ describe('OOC Resolve에서 Role 기반 승인', () => {
266
+ it('OOC Resolve Activity에 outlierApprovalLine(Role 타입)이 적용되어야 한다', async () => {
267
+ await withTestTransaction(async (context) => {
268
+ const { tx } = context.state
269
+ const domain = await domainFactory.create({}, tx)
270
+ const approverRole = await roleFactory.create({ name: 'OOC Approver', domain }, tx)
271
+
272
+ // Given: OOC Resolve Activity with Role-based approval line
273
+ const resolveActivity = await activityFactory.createOocResolveActivity(domain, tx)
274
+ const outlierApprovalLine = [createRoleApprovalLineItem(approverRole)]
275
+
276
+ // When: Instance 생성
277
+ const instance = await activityInstanceFactory.createWithActivity(
278
+ {
279
+ state: ActivityInstanceStatus.Issued,
280
+ approvalLine: outlierApprovalLine
281
+ },
282
+ resolveActivity,
283
+ domain,
284
+ tx
285
+ )
286
+
287
+ // Then
288
+ expect(instance.approvalLine?.length).toBe(1)
289
+ expect(instance.approvalLine?.[0].type).toBe('Role')
290
+ })
291
+ })
292
+
293
+ it('OOC Resolve 승인 시 Role을 가진 사용자만 승인 가능해야 한다', async () => {
294
+ await withTestTransaction(async (context) => {
295
+ const { tx } = context.state
296
+ const domain = await domainFactory.create({}, tx)
297
+ const approverRole = await roleFactory.create({ name: 'OOC Approver', domain }, tx)
298
+ const userWithRole = await userFactory.createWithRole({}, approverRole, tx)
299
+
300
+ // Given: Role 기반 approval
301
+ const approval = await activityApprovalFactory.createWithRole(
302
+ { judgment: ActivityApprovalJudgment.Pending },
303
+ undefined,
304
+ approverRole,
305
+ tx
306
+ )
307
+
308
+ // When: Role을 가진 사용자가 승인
309
+ const approved = await simulateApproval(
310
+ approval,
311
+ userWithRole,
312
+ 'OOC Corrective action approved',
313
+ { tx, domain, user: userWithRole }
314
+ )
315
+
316
+ // Then
317
+ expect(approved.judgment).toBe(ActivityApprovalJudgment.Approved)
318
+ expect(approved.terminatedAt).toBeDefined()
319
+ })
320
+ })
321
+ })
322
+
323
+ describe('ActivityApprovalJudgment Enum', () => {
324
+ it('Pending은 빈 문자열이어야 한다', () => {
325
+ expect(ActivityApprovalJudgment.Pending).toBe('')
326
+ })
327
+
328
+ it('모든 judgment 값이 정의되어야 한다', () => {
329
+ expect(ActivityApprovalJudgment.Rejected).toBe('rejected')
330
+ expect(ActivityApprovalJudgment.Escalated).toBe('escalated')
331
+ expect(ActivityApprovalJudgment.Delegated).toBe('delegated')
332
+ expect(ActivityApprovalJudgment.Approved).toBe('approved')
333
+ expect(ActivityApprovalJudgment.Aborted).toBe('aborted')
334
+ })
335
+ })
336
+
337
+ describe('Edge Cases', () => {
338
+ it('Role에 사용자가 없어도 approval이 생성되어야 한다', async () => {
339
+ await withTestTransaction(async (context) => {
340
+ const { tx } = context.state
341
+ const domain = await domainFactory.create({}, tx)
342
+
343
+ // Given: 사용자가 없는 Role
344
+ const emptyRole = await roleFactory.create({ name: 'Empty Role', domain }, tx)
345
+
346
+ // When: Role 기반 approval 생성
347
+ const approval = await activityApprovalFactory.createWithRole(
348
+ { judgment: ActivityApprovalJudgment.Pending },
349
+ undefined,
350
+ emptyRole,
351
+ tx
352
+ )
353
+
354
+ // Then: approval은 생성됨 (나중에 Role에 사용자가 추가될 수 있음)
355
+ expect(approval).toBeDefined()
356
+ expect(approval.approverRole?.id).toBe(emptyRole.id)
357
+ })
358
+ })
359
+
360
+ it('승인 완료 시 terminatedAt이 설정되어야 한다', async () => {
361
+ await withTestTransaction(async (context) => {
362
+ const { tx } = context.state
363
+ const domain = await domainFactory.create({}, tx)
364
+ const approverRole = await roleFactory.create({ name: 'Approver', domain }, tx)
365
+ const approver = await userFactory.createWithRole({}, approverRole, tx)
366
+
367
+ // Given
368
+ const approval = await activityApprovalFactory.createWithRole(
369
+ { judgment: ActivityApprovalJudgment.Pending },
370
+ undefined,
371
+ approverRole,
372
+ tx
373
+ )
374
+
375
+ // When
376
+ const approved = await simulateApproval(
377
+ approval,
378
+ approver,
379
+ undefined,
380
+ { tx, domain, user: approver }
381
+ )
382
+
383
+ // Then
384
+ expect(approved.terminatedAt).toBeDefined()
385
+ expect(approved.terminatedAt).toBeInstanceOf(Date)
386
+ })
387
+ })
388
+ })
389
+ })