@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,360 @@
1
+ /**
2
+ * ActivityInstance Issue Controller Tests
3
+ * ActivityInstance 발행 컨트롤러 테스트
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
+ } from '../../../../../test/factories'
16
+ import { ActivityInstanceStatus, ActivityThreadStatus } from '../../../../../test/entities/schemas'
17
+
18
+ describe('ActivityInstance Issue Controller', () => {
19
+ let testDb: TestDatabase
20
+
21
+ beforeAll(async () => {
22
+ testDb = TestDatabase.getInstance()
23
+ })
24
+
25
+ describe('Instance 발행 기본 동작', () => {
26
+ it('Activity에서 Instance를 발행할 수 있어야 한다', async () => {
27
+ await withTestTransaction(async (context) => {
28
+ const { tx, domain, user } = context.state
29
+
30
+ // Given: Activity 생성
31
+ const activity = await activityFactory.createWithDomain(
32
+ {
33
+ name: 'Test Activity',
34
+ description: 'Test Description',
35
+ priority: 3,
36
+ standardTime: 3600 // 1시간
37
+ },
38
+ domain,
39
+ tx
40
+ )
41
+
42
+ // When: Instance 발행 시뮬레이션
43
+ const issuedAt = new Date()
44
+ const instance = await activityInstanceFactory.createWithActivity(
45
+ {
46
+ name: activity.name,
47
+ description: activity.description,
48
+ priority: activity.priority,
49
+ state: ActivityInstanceStatus.Issued,
50
+ input: { taskId: 'task-123' }
51
+ },
52
+ activity,
53
+ domain,
54
+ tx
55
+ )
56
+
57
+ // Then
58
+ expect(instance).toBeDefined()
59
+ expect(instance.state).toBe(ActivityInstanceStatus.Issued)
60
+ expect(instance.name).toBe('Test Activity')
61
+ expect(instance.priority).toBe(3)
62
+ expect(instance.activity?.id).toBe(activity.id)
63
+ })
64
+ })
65
+
66
+ it('assigneeRole이 설정되어야 한다', async () => {
67
+ await withTestTransaction(async (context) => {
68
+ const { tx, domain } = context.state
69
+
70
+ // Given
71
+ const assigneeRole = await roleFactory.create({ name: 'Worker', domain }, tx)
72
+ const activity = await activityFactory.createWithDomain({}, domain, tx)
73
+
74
+ // When
75
+ const instance = await activityInstanceFactory.createWithActivity(
76
+ {
77
+ state: ActivityInstanceStatus.Issued,
78
+ assigneeRole
79
+ },
80
+ activity,
81
+ domain,
82
+ tx
83
+ )
84
+
85
+ // Then
86
+ expect(instance.assigneeRole?.id).toBe(assigneeRole.id)
87
+ })
88
+ })
89
+
90
+ it('dueAt이 standardTime 기반으로 계산되어야 한다', async () => {
91
+ await withTestTransaction(async (context) => {
92
+ const { tx, domain } = context.state
93
+
94
+ // Given
95
+ const standardTime = 7200 // 2시간
96
+ const activity = await activityFactory.createWithDomain(
97
+ { standardTime },
98
+ domain,
99
+ tx
100
+ )
101
+ const issuedAt = new Date()
102
+
103
+ // When: dueAt 계산
104
+ const expectedDueAt = new Date(issuedAt.getTime() + standardTime * 1000)
105
+
106
+ const instance = await activityInstanceFactory.createWithActivity(
107
+ {
108
+ state: ActivityInstanceStatus.Issued,
109
+ dueAt: expectedDueAt
110
+ },
111
+ activity,
112
+ domain,
113
+ tx
114
+ )
115
+
116
+ // Then
117
+ expect(instance.dueAt).toEqual(expectedDueAt)
118
+ })
119
+ })
120
+ })
121
+
122
+ describe('Thread 생성 규칙', () => {
123
+ it('assignees가 지정되면 해당 사용자에게 Thread가 할당되어야 한다', async () => {
124
+ await withTestTransaction(async (context) => {
125
+ const { tx, domain } = context.state
126
+
127
+ // Given
128
+ const assignee = await userFactory.create({ name: 'Assigned User' }, tx)
129
+ const instance = await activityInstanceFactory.createWithActivity(
130
+ { state: ActivityInstanceStatus.Issued },
131
+ undefined,
132
+ domain,
133
+ tx
134
+ )
135
+
136
+ // When: Thread 생성
137
+ const thread = await activityThreadFactory.createWithInstanceAndAssignee(
138
+ { state: ActivityThreadStatus.Assigned },
139
+ instance,
140
+ assignee,
141
+ tx
142
+ )
143
+
144
+ // Then
145
+ expect(thread.assignee?.id).toBe(assignee.id)
146
+ expect(thread.state).toBe(ActivityThreadStatus.Assigned)
147
+ })
148
+ })
149
+
150
+ it('assigneeRole만 있고 assignees가 없으면 Unassigned Thread가 생성되어야 한다', async () => {
151
+ await withTestTransaction(async (context) => {
152
+ const { tx, domain } = context.state
153
+
154
+ // Given
155
+ const assigneeRole = await roleFactory.create({ name: 'Worker', domain }, tx)
156
+ const instance = await activityInstanceFactory.createWithActivity(
157
+ {
158
+ state: ActivityInstanceStatus.Issued,
159
+ assigneeRole,
160
+ threadsMin: 1
161
+ },
162
+ undefined,
163
+ domain,
164
+ tx
165
+ )
166
+
167
+ // When: Unassigned Thread 생성
168
+ const thread = await tx.save('ActivityThread', {
169
+ state: ActivityThreadStatus.Unassigned,
170
+ round: 1,
171
+ activityInstance: instance,
172
+ domain
173
+ }) as any
174
+
175
+ // Then
176
+ expect(thread.state).toBe(ActivityThreadStatus.Unassigned)
177
+ expect(thread.assignee).toBeUndefined()
178
+ })
179
+ })
180
+
181
+ it('threadsMin이 0이면 assigneeRole의 모든 사용자에게 Thread가 생성되어야 한다', async () => {
182
+ await withTestTransaction(async (context) => {
183
+ const { tx, domain } = context.state
184
+
185
+ // Given: Role에 여러 사용자
186
+ const role = await roleFactory.create({ name: 'Worker', domain }, tx)
187
+ const user1 = await userFactory.create({ name: 'Worker 1', roles: [role] }, tx)
188
+ const user2 = await userFactory.create({ name: 'Worker 2', roles: [role] }, tx)
189
+
190
+ const instance = await activityInstanceFactory.createWithActivity(
191
+ {
192
+ state: ActivityInstanceStatus.Issued,
193
+ assigneeRole: role,
194
+ threadsMin: 0 // 모든 사용자
195
+ },
196
+ undefined,
197
+ domain,
198
+ tx
199
+ )
200
+
201
+ // When: 모든 Role 사용자에게 Thread 생성 시뮬레이션
202
+ const threads = await Promise.all([
203
+ activityThreadFactory.createWithInstanceAndAssignee(
204
+ { state: ActivityThreadStatus.Assigned },
205
+ instance,
206
+ user1,
207
+ tx
208
+ ),
209
+ activityThreadFactory.createWithInstanceAndAssignee(
210
+ { state: ActivityThreadStatus.Assigned },
211
+ instance,
212
+ user2,
213
+ tx
214
+ )
215
+ ])
216
+
217
+ // Then
218
+ expect(threads.length).toBe(2)
219
+ expect(threads[0].assignee?.id).toBe(user1.id)
220
+ expect(threads[1].assignee?.id).toBe(user2.id)
221
+ })
222
+ })
223
+ })
224
+
225
+ describe('Draft -> Issue 전이', () => {
226
+ it('Draft 상태에서만 Issue가 가능해야 한다', async () => {
227
+ await withTestTransaction(async (context) => {
228
+ const { tx, domain } = context.state
229
+
230
+ // Given: Draft 상태의 Instance
231
+ const draftInstance = await activityInstanceFactory.createWithActivity(
232
+ { state: ActivityInstanceStatus.Draft },
233
+ undefined,
234
+ domain,
235
+ tx
236
+ )
237
+
238
+ // When: Issue 시뮬레이션
239
+ draftInstance.state = ActivityInstanceStatus.Issued
240
+ const issued = await tx.save('ActivityInstance', draftInstance)
241
+
242
+ // Then
243
+ expect(issued.state).toBe(ActivityInstanceStatus.Issued)
244
+ })
245
+ })
246
+
247
+ it('이미 Issue된 Instance는 다시 Issue할 수 없어야 한다', async () => {
248
+ await withTestTransaction(async (context) => {
249
+ const { tx, domain } = context.state
250
+
251
+ // Given: 이미 Issued 상태의 Instance
252
+ const issuedInstance = await activityInstanceFactory.createWithActivity(
253
+ { state: ActivityInstanceStatus.Issued },
254
+ undefined,
255
+ domain,
256
+ tx
257
+ )
258
+
259
+ // Then: 이미 Issued 상태
260
+ expect(issuedInstance.state).toBe(ActivityInstanceStatus.Issued)
261
+ // 실제 컨트롤러에서는 이 경우 에러 발생
262
+ })
263
+ })
264
+ })
265
+
266
+ describe('Post 타입 발행 (startingType=post)', () => {
267
+ it('post 타입이면 발행과 동시에 Started 상태가 되어야 한다', async () => {
268
+ await withTestTransaction(async (context) => {
269
+ const { tx, domain, user } = context.state
270
+
271
+ // Given: startingType=post인 Activity 시뮬레이션
272
+ // When: post 방식 발행 시뮬레이션
273
+ const instance = await activityInstanceFactory.createWithActivity(
274
+ {
275
+ state: ActivityInstanceStatus.Started, // post는 바로 Started
276
+ input: { dataId: 'data-123' },
277
+ output: { result: 'submitted' }
278
+ },
279
+ undefined,
280
+ domain,
281
+ tx
282
+ )
283
+
284
+ // post 방식은 Thread도 바로 생성됨
285
+ const thread = await activityThreadFactory.createWithInstanceAndAssignee(
286
+ {
287
+ state: ActivityThreadStatus.Started,
288
+ startedAt: new Date()
289
+ },
290
+ instance,
291
+ user,
292
+ tx
293
+ )
294
+
295
+ // Then
296
+ expect(instance.state).toBe(ActivityInstanceStatus.Started)
297
+ expect(thread.state).toBe(ActivityThreadStatus.Started)
298
+ })
299
+ })
300
+ })
301
+
302
+ describe('approvalLine 설정', () => {
303
+ it('Activity의 approvalLine이 Instance에 복사되어야 한다', async () => {
304
+ await withTestTransaction(async (context) => {
305
+ const { tx, domain } = context.state
306
+
307
+ // Given
308
+ const approverRole = await roleFactory.create({ name: 'Approver', domain }, tx)
309
+ const approvalLine = [
310
+ { type: 'Role', value: approverRole.id, approver: { id: approverRole.id, name: approverRole.name } }
311
+ ]
312
+
313
+ // When
314
+ const instance = await activityInstanceFactory.createWithActivity(
315
+ {
316
+ state: ActivityInstanceStatus.Issued,
317
+ approvalLine
318
+ },
319
+ undefined,
320
+ domain,
321
+ tx
322
+ )
323
+
324
+ // Then
325
+ expect(instance.approvalLine).toBeDefined()
326
+ expect(instance.approvalLine?.length).toBe(1)
327
+ expect(instance.approvalLine?.[0].type).toBe('Role')
328
+ })
329
+ })
330
+ })
331
+
332
+ describe('input/output 처리', () => {
333
+ it('input이 Instance에 저장되어야 한다', async () => {
334
+ await withTestTransaction(async (context) => {
335
+ const { tx, domain } = context.state
336
+
337
+ // Given
338
+ const input = {
339
+ dataOocId: 'ooc-123',
340
+ instruction: 'Fix the temperature'
341
+ }
342
+
343
+ // When
344
+ const instance = await activityInstanceFactory.createWithActivity(
345
+ {
346
+ state: ActivityInstanceStatus.Issued,
347
+ input
348
+ },
349
+ undefined,
350
+ domain,
351
+ tx
352
+ )
353
+
354
+ // Then
355
+ expect(instance.input).toEqual(input)
356
+ expect(instance.input?.dataOocId).toBe('ooc-123')
357
+ })
358
+ })
359
+ })
360
+ })