@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.
- package/dist-client/components/activity-thread-timeline.d.ts +1 -9
- package/dist-client/components/activity-thread-timeline.js +1 -3
- package/dist-client/components/activity-thread-timeline.js.map +1 -1
- package/dist-client/pages/activity/activity-list-page.d.ts +1 -7
- package/dist-client/pages/activity/activity-list-page.js +125 -234
- package/dist-client/pages/activity/activity-list-page.js.map +1 -1
- package/dist-client/pages/activity/activity-page.d.ts +1 -7
- package/dist-client/pages/activity/activity-page.js +51 -93
- package/dist-client/pages/activity/activity-page.js.map +1 -1
- package/dist-client/pages/activity/starter-list-page.d.ts +1 -7
- package/dist-client/pages/activity/starter-list-page.js +33 -62
- package/dist-client/pages/activity/starter-list-page.js.map +1 -1
- package/dist-client/pages/activity-approval/activity-approval-list-page.d.ts +1 -7
- package/dist-client/pages/activity-approval/activity-approval-list-page.js +50 -95
- package/dist-client/pages/activity-approval/activity-approval-list-page.js.map +1 -1
- package/dist-client/pages/activity-approval/activity-approval-page.d.ts +1 -7
- package/dist-client/pages/activity-approval/activity-approval-page.js +73 -119
- package/dist-client/pages/activity-approval/activity-approval-page.js.map +1 -1
- package/dist-client/pages/activity-instance/activity-instance-list-page.d.ts +0 -6
- package/dist-client/pages/activity-instance/activity-instance-list-page.js +63 -120
- package/dist-client/pages/activity-instance/activity-instance-list-page.js.map +1 -1
- package/dist-client/pages/activity-instance/activity-instance-search-page.d.ts +1 -7
- package/dist-client/pages/activity-instance/activity-instance-search-page.js +55 -101
- package/dist-client/pages/activity-instance/activity-instance-search-page.js.map +1 -1
- package/dist-client/pages/activity-instance/activity-instance-start-page.d.ts +1 -7
- package/dist-client/pages/activity-instance/activity-instance-start-page.js +65 -109
- package/dist-client/pages/activity-instance/activity-instance-start-page.js.map +1 -1
- package/dist-client/pages/activity-stats/activity-stats-list-page.d.ts +1 -7
- package/dist-client/pages/activity-stats/activity-stats-list-page.js +50 -95
- package/dist-client/pages/activity-stats/activity-stats-list-page.js.map +1 -1
- package/dist-client/pages/activity-store/activity-store-page.d.ts +1 -7
- package/dist-client/pages/activity-store/activity-store-page.js +2 -3
- package/dist-client/pages/activity-store/activity-store-page.js.map +1 -1
- package/dist-client/pages/activity-supervisor/reporter-list-page.d.ts +1 -7
- package/dist-client/pages/activity-supervisor/reporter-list-page.js +36 -66
- package/dist-client/pages/activity-supervisor/reporter-list-page.js.map +1 -1
- package/dist-client/pages/activity-template/activity-template-list-page.d.ts +1 -7
- package/dist-client/pages/activity-template/activity-template-list-page.js +70 -134
- package/dist-client/pages/activity-template/activity-template-list-page.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-list-page.d.ts +1 -7
- package/dist-client/pages/activity-thread/activity-thread-list-page.js +49 -93
- package/dist-client/pages/activity-thread/activity-thread-list-page.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-page.d.ts +1 -7
- package/dist-client/pages/activity-thread/activity-thread-page.js +80 -135
- package/dist-client/pages/activity-thread/activity-thread-page.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-view-page.d.ts +1 -7
- package/dist-client/pages/activity-thread/activity-thread-view-page.js +54 -80
- package/dist-client/pages/activity-thread/activity-thread-view-page.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-view.js +4 -0
- package/dist-client/pages/activity-thread/activity-thread-view.js.map +1 -1
- package/dist-client/pages/dashboard/dashboard-home.js +3 -5
- package/dist-client/pages/dashboard/dashboard-home.js.map +1 -1
- package/dist-client/pages/installable-activity/installable-activity-list-page.d.ts +0 -6
- package/dist-client/pages/installable-activity/installable-activity-list-page.js +68 -130
- package/dist-client/pages/installable-activity/installable-activity-list-page.js.map +1 -1
- package/dist-client/pages/todo/approval-done-list-page.d.ts +1 -7
- package/dist-client/pages/todo/approval-done-list-page.js +53 -100
- package/dist-client/pages/todo/approval-done-list-page.js.map +1 -1
- package/dist-client/pages/todo/approval-pending-list-page.d.ts +0 -6
- package/dist-client/pages/todo/approval-pending-list-page.js +63 -119
- package/dist-client/pages/todo/approval-pending-list-page.js.map +1 -1
- package/dist-client/pages/todo/done-list-calendar-page.d.ts +1 -7
- package/dist-client/pages/todo/done-list-calendar-page.js +2 -3
- package/dist-client/pages/todo/done-list-calendar-page.js.map +1 -1
- package/dist-client/pages/todo/done-list-page.d.ts +1 -7
- package/dist-client/pages/todo/done-list-page.js +56 -106
- package/dist-client/pages/todo/done-list-page.js.map +1 -1
- package/dist-client/pages/todo/draft-list-page.d.ts +1 -7
- package/dist-client/pages/todo/draft-list-page.js +49 -88
- package/dist-client/pages/todo/draft-list-page.js.map +1 -1
- package/dist-client/pages/todo/pickable-list-page.d.ts +1 -7
- package/dist-client/pages/todo/pickable-list-page.js +48 -91
- package/dist-client/pages/todo/pickable-list-page.js.map +1 -1
- package/dist-client/pages/todo/todo-list-page.d.ts +0 -6
- package/dist-client/pages/todo/todo-list-page.js +56 -106
- package/dist-client/pages/todo/todo-list-page.js.map +1 -1
- package/dist-client/pages/worklist-home.js +2 -3
- package/dist-client/pages/worklist-home.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/templates/activity-approval-context-template.js +8 -12
- package/dist-client/templates/activity-approval-context-template.js.map +1 -1
- package/dist-client/templates/activity-instance-context-template.js +8 -12
- package/dist-client/templates/activity-instance-context-template.js.map +1 -1
- package/dist-client/templates/activity-thread-context-template.js +8 -12
- package/dist-client/templates/activity-thread-context-template.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/activity-approval/approve.js +2 -2
- package/dist-server/controllers/activity-approval/approve.js.map +1 -1
- package/dist-server/controllers/activity-thread/submit.js +2 -2
- package/dist-server/controllers/activity-thread/submit.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -2
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -16
- package/spec/integration/approval-mixed-types.spec.ts +491 -0
- package/spec/integration/approval-role-based.spec.ts +389 -0
- package/spec/integration/instance-lifecycle.spec.ts +406 -0
- package/spec/integration/role-approval-edge-cases.spec.ts +581 -0
- package/spec/unit/controllers/activity-instance-issue.spec.ts +360 -0
- package/spec/unit/controllers/activity-thread-submit.spec.ts +384 -0
- package/spec/unit/role-approval-escalate-logic.spec.ts +499 -0
- package/spec/unit/role-approval-submit-logic.spec.ts +481 -0
- package/spec/unit/thread-state-helpers.spec.ts +253 -0
- package/translations/en.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/worklist",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0-beta.2",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"browser": "dist-client/index.js",
|
|
6
6
|
"things-factory": true,
|
|
@@ -28,20 +28,20 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@material/web": "^2.0.0",
|
|
31
|
-
"@operato/event-view": "^
|
|
32
|
-
"@operato/graphql": "^
|
|
33
|
-
"@operato/grist-editor": "^
|
|
34
|
-
"@operato/moment-timezone-es": "^
|
|
35
|
-
"@operato/p13n": "^
|
|
36
|
-
"@things-factory/attachment-base": "^
|
|
37
|
-
"@things-factory/auth-base": "^
|
|
38
|
-
"@things-factory/board-service": "^
|
|
39
|
-
"@things-factory/board-ui": "^
|
|
40
|
-
"@things-factory/context-ui": "^
|
|
41
|
-
"@things-factory/organization": "^
|
|
42
|
-
"@things-factory/personalization": "^
|
|
43
|
-
"@things-factory/scheduler-client": "^
|
|
44
|
-
"@things-factory/shell": "^
|
|
31
|
+
"@operato/event-view": "^10.0.0-beta.1",
|
|
32
|
+
"@operato/graphql": "^10.0.0-beta.1",
|
|
33
|
+
"@operato/grist-editor": "^10.0.0-beta.1",
|
|
34
|
+
"@operato/moment-timezone-es": "^10.0.0-beta.1",
|
|
35
|
+
"@operato/p13n": "^10.0.0-beta.1",
|
|
36
|
+
"@things-factory/attachment-base": "^10.0.0-beta.2",
|
|
37
|
+
"@things-factory/auth-base": "^10.0.0-beta.2",
|
|
38
|
+
"@things-factory/board-service": "^10.0.0-beta.2",
|
|
39
|
+
"@things-factory/board-ui": "^10.0.0-beta.2",
|
|
40
|
+
"@things-factory/context-ui": "^10.0.0-beta.2",
|
|
41
|
+
"@things-factory/organization": "^10.0.0-beta.2",
|
|
42
|
+
"@things-factory/personalization": "^10.0.0-beta.2",
|
|
43
|
+
"@things-factory/scheduler-client": "^10.0.0-beta.2",
|
|
44
|
+
"@things-factory/shell": "^10.0.0-beta.2"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "ea8a08ff802ae28a37fdda605b6356482e7a6faf"
|
|
47
47
|
}
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mixed Approval Line Type Integration Tests
|
|
3
|
+
* ROLE과 User 타입이 혼합된 다단계 승인라인 통합 테스트
|
|
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
|
+
activityInstanceFactory,
|
|
13
|
+
activityThreadFactory,
|
|
14
|
+
activityApprovalFactory
|
|
15
|
+
} from '../../../../test/factories'
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
ActivityInstanceStatus,
|
|
19
|
+
ActivityThreadStatus,
|
|
20
|
+
ActivityApprovalJudgment
|
|
21
|
+
} from '../../../../test/entities/schemas'
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
createRoleApprovalLineItem,
|
|
25
|
+
createUserApprovalLineItem,
|
|
26
|
+
createMultiLevelApprovalLine
|
|
27
|
+
} from '../../../../test/helpers/workflow-helpers'
|
|
28
|
+
|
|
29
|
+
describe('Mixed Approval Line Type Integration Tests', () => {
|
|
30
|
+
let testDb: TestDatabase
|
|
31
|
+
|
|
32
|
+
beforeAll(async () => {
|
|
33
|
+
testDb = TestDatabase.getInstance()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe('ROLE → User 혼합 승인라인', () => {
|
|
37
|
+
it('첫 번째 레벨 ROLE, 두 번째 레벨 User 승인라인이 처리되어야 한다', async () => {
|
|
38
|
+
await withTestTransaction(async (context) => {
|
|
39
|
+
const { tx } = context.state
|
|
40
|
+
const domain = await domainFactory.create({}, tx)
|
|
41
|
+
const firstRole = await roleFactory.create({ name: 'First Approver Role', domain }, tx)
|
|
42
|
+
const secondUser = await userFactory.create({ name: 'Final Approver' }, tx)
|
|
43
|
+
const firstApprover = await userFactory.createWithRole({}, firstRole, tx)
|
|
44
|
+
|
|
45
|
+
// Given: ROLE → User 혼합 승인라인
|
|
46
|
+
const approvalLine = [
|
|
47
|
+
createRoleApprovalLineItem(firstRole),
|
|
48
|
+
createUserApprovalLineItem(secondUser)
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
// 타입 확인
|
|
52
|
+
expect(approvalLine[0].type).toBe('Role')
|
|
53
|
+
expect(approvalLine[1].type).toBe('Employee')
|
|
54
|
+
|
|
55
|
+
const instance = await activityInstanceFactory.createWithApprovalLine(
|
|
56
|
+
approvalLine,
|
|
57
|
+
{ state: ActivityInstanceStatus.Started },
|
|
58
|
+
undefined,
|
|
59
|
+
domain,
|
|
60
|
+
tx
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
64
|
+
{ state: ActivityThreadStatus.Submitted },
|
|
65
|
+
instance,
|
|
66
|
+
firstApprover,
|
|
67
|
+
tx
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
// When: 첫 번째 ROLE 승인 (에스컬레이션)
|
|
71
|
+
const firstApproval = await tx.save('ActivityApproval', {
|
|
72
|
+
domain,
|
|
73
|
+
activityThread: thread,
|
|
74
|
+
round: 1,
|
|
75
|
+
order: 1,
|
|
76
|
+
judgment: ActivityApprovalJudgment.Escalated,
|
|
77
|
+
approverRole: firstRole,
|
|
78
|
+
approver: firstApprover,
|
|
79
|
+
terminatedAt: new Date(),
|
|
80
|
+
creator: firstApprover,
|
|
81
|
+
updater: firstApprover
|
|
82
|
+
}) as any
|
|
83
|
+
|
|
84
|
+
expect(firstApproval.judgment).toBe(ActivityApprovalJudgment.Escalated)
|
|
85
|
+
expect(firstApproval.approverRole?.id).toBe(firstRole.id)
|
|
86
|
+
|
|
87
|
+
// When: 두 번째 레벨 User 승인 생성
|
|
88
|
+
const order = firstApproval.order // 1
|
|
89
|
+
const nextLineItem = approvalLine[order] // approvalLine[1]
|
|
90
|
+
expect(nextLineItem.type).toBe('Employee')
|
|
91
|
+
|
|
92
|
+
const secondApproval = await tx.save('ActivityApproval', {
|
|
93
|
+
domain,
|
|
94
|
+
activityThread: thread,
|
|
95
|
+
round: 1,
|
|
96
|
+
order: order + 1,
|
|
97
|
+
judgment: ActivityApprovalJudgment.Pending,
|
|
98
|
+
approver: secondUser,
|
|
99
|
+
transaction: 'escalate',
|
|
100
|
+
creator: firstApprover,
|
|
101
|
+
updater: firstApprover
|
|
102
|
+
}) as any
|
|
103
|
+
|
|
104
|
+
// Then: User 타입 approval 생성됨
|
|
105
|
+
expect(secondApproval.order).toBe(2)
|
|
106
|
+
expect(secondApproval.approver?.id).toBe(secondUser.id)
|
|
107
|
+
expect(secondApproval.approverRole).toBeUndefined()
|
|
108
|
+
|
|
109
|
+
// When: 최종 User 승인
|
|
110
|
+
secondApproval.judgment = ActivityApprovalJudgment.Approved
|
|
111
|
+
secondApproval.terminatedAt = new Date()
|
|
112
|
+
const approved = await tx.save('ActivityApproval', secondApproval) as any
|
|
113
|
+
|
|
114
|
+
expect(approved.judgment).toBe(ActivityApprovalJudgment.Approved)
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('User → ROLE 혼합 승인라인', () => {
|
|
120
|
+
it('첫 번째 레벨 User, 두 번째 레벨 ROLE 승인라인이 처리되어야 한다', async () => {
|
|
121
|
+
await withTestTransaction(async (context) => {
|
|
122
|
+
const { tx } = context.state
|
|
123
|
+
const domain = await domainFactory.create({}, tx)
|
|
124
|
+
const firstUser = await userFactory.create({ name: 'First Approver' }, tx)
|
|
125
|
+
const secondRole = await roleFactory.create({ name: 'Final Approver Role', domain }, tx)
|
|
126
|
+
const secondApprover = await userFactory.createWithRole({}, secondRole, tx)
|
|
127
|
+
|
|
128
|
+
// Given: User → ROLE 혼합 승인라인
|
|
129
|
+
const approvalLine = [
|
|
130
|
+
createUserApprovalLineItem(firstUser),
|
|
131
|
+
createRoleApprovalLineItem(secondRole)
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
expect(approvalLine[0].type).toBe('Employee')
|
|
135
|
+
expect(approvalLine[1].type).toBe('Role')
|
|
136
|
+
|
|
137
|
+
const instance = await activityInstanceFactory.createWithApprovalLine(
|
|
138
|
+
approvalLine,
|
|
139
|
+
{ state: ActivityInstanceStatus.Started },
|
|
140
|
+
undefined,
|
|
141
|
+
domain,
|
|
142
|
+
tx
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
146
|
+
{ state: ActivityThreadStatus.Submitted },
|
|
147
|
+
instance,
|
|
148
|
+
firstUser,
|
|
149
|
+
tx
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
// When: 첫 번째 User 승인 (에스컬레이션)
|
|
153
|
+
const firstApproval = await tx.save('ActivityApproval', {
|
|
154
|
+
domain,
|
|
155
|
+
activityThread: thread,
|
|
156
|
+
round: 1,
|
|
157
|
+
order: 1,
|
|
158
|
+
judgment: ActivityApprovalJudgment.Escalated,
|
|
159
|
+
approver: firstUser,
|
|
160
|
+
terminatedAt: new Date(),
|
|
161
|
+
creator: firstUser,
|
|
162
|
+
updater: firstUser
|
|
163
|
+
}) as any
|
|
164
|
+
|
|
165
|
+
expect(firstApproval.judgment).toBe(ActivityApprovalJudgment.Escalated)
|
|
166
|
+
expect(firstApproval.approver?.id).toBe(firstUser.id)
|
|
167
|
+
|
|
168
|
+
// When: 두 번째 레벨 ROLE 승인 생성
|
|
169
|
+
const order = firstApproval.order // 1
|
|
170
|
+
const nextLineItem = approvalLine[order] // approvalLine[1]
|
|
171
|
+
expect(nextLineItem.type).toBe('Role')
|
|
172
|
+
|
|
173
|
+
const secondApproval = await tx.save('ActivityApproval', {
|
|
174
|
+
domain,
|
|
175
|
+
activityThread: thread,
|
|
176
|
+
round: 1,
|
|
177
|
+
order: order + 1,
|
|
178
|
+
judgment: ActivityApprovalJudgment.Pending,
|
|
179
|
+
approverRole: secondRole,
|
|
180
|
+
transaction: 'escalate',
|
|
181
|
+
creator: firstUser,
|
|
182
|
+
updater: firstUser
|
|
183
|
+
}) as any
|
|
184
|
+
|
|
185
|
+
// Then: ROLE 타입 approval 생성됨
|
|
186
|
+
expect(secondApproval.order).toBe(2)
|
|
187
|
+
expect(secondApproval.approverRole?.id).toBe(secondRole.id)
|
|
188
|
+
expect(secondApproval.approver).toBeUndefined()
|
|
189
|
+
|
|
190
|
+
// When: ROLE 소유자가 최종 승인
|
|
191
|
+
secondApproval.judgment = ActivityApprovalJudgment.Approved
|
|
192
|
+
secondApproval.approver = secondApprover
|
|
193
|
+
secondApproval.terminatedAt = new Date()
|
|
194
|
+
const approved = await tx.save('ActivityApproval', secondApproval) as any
|
|
195
|
+
|
|
196
|
+
expect(approved.judgment).toBe(ActivityApprovalJudgment.Approved)
|
|
197
|
+
expect(approved.approverRole?.id).toBe(secondRole.id)
|
|
198
|
+
expect(approved.approver?.id).toBe(secondApprover.id)
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
describe('User → ROLE → User 3단계 혼합', () => {
|
|
204
|
+
it('3단계 혼합 승인라인이 순서대로 처리되어야 한다', async () => {
|
|
205
|
+
await withTestTransaction(async (context) => {
|
|
206
|
+
const { tx } = context.state
|
|
207
|
+
const domain = await domainFactory.create({}, tx)
|
|
208
|
+
|
|
209
|
+
const firstUser = await userFactory.create({ name: 'Team Lead' }, tx)
|
|
210
|
+
const middleRole = await roleFactory.create({ name: 'Manager Role', domain }, tx)
|
|
211
|
+
const finalUser = await userFactory.create({ name: 'Director' }, tx)
|
|
212
|
+
const middleApprover = await userFactory.createWithRole({ name: 'Manager' }, middleRole, tx)
|
|
213
|
+
|
|
214
|
+
// Given: User → ROLE → User 3단계 승인라인
|
|
215
|
+
const approvalLine = [
|
|
216
|
+
createUserApprovalLineItem(firstUser),
|
|
217
|
+
createRoleApprovalLineItem(middleRole),
|
|
218
|
+
createUserApprovalLineItem(finalUser)
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
expect(approvalLine[0].type).toBe('Employee')
|
|
222
|
+
expect(approvalLine[1].type).toBe('Role')
|
|
223
|
+
expect(approvalLine[2].type).toBe('Employee')
|
|
224
|
+
|
|
225
|
+
const instance = await activityInstanceFactory.createWithApprovalLine(
|
|
226
|
+
approvalLine,
|
|
227
|
+
{ state: ActivityInstanceStatus.Started },
|
|
228
|
+
undefined,
|
|
229
|
+
domain,
|
|
230
|
+
tx
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
234
|
+
{ state: ActivityThreadStatus.Submitted },
|
|
235
|
+
instance,
|
|
236
|
+
firstUser,
|
|
237
|
+
tx
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
// Level 1: User 승인 → Escalated
|
|
241
|
+
const approval1 = await tx.save('ActivityApproval', {
|
|
242
|
+
domain,
|
|
243
|
+
activityThread: thread,
|
|
244
|
+
round: 1,
|
|
245
|
+
order: 1,
|
|
246
|
+
judgment: ActivityApprovalJudgment.Escalated,
|
|
247
|
+
approver: firstUser,
|
|
248
|
+
terminatedAt: new Date(),
|
|
249
|
+
creator: firstUser,
|
|
250
|
+
updater: firstUser
|
|
251
|
+
}) as any
|
|
252
|
+
|
|
253
|
+
// Level 2: ROLE 승인 → Escalated
|
|
254
|
+
const nextType2 = approvalLine[1].type
|
|
255
|
+
expect(nextType2).toBe('Role')
|
|
256
|
+
|
|
257
|
+
const approval2 = await tx.save('ActivityApproval', {
|
|
258
|
+
domain,
|
|
259
|
+
activityThread: thread,
|
|
260
|
+
round: 1,
|
|
261
|
+
order: 2,
|
|
262
|
+
judgment: ActivityApprovalJudgment.Escalated,
|
|
263
|
+
approverRole: middleRole,
|
|
264
|
+
approver: middleApprover,
|
|
265
|
+
terminatedAt: new Date(),
|
|
266
|
+
creator: middleApprover,
|
|
267
|
+
updater: middleApprover
|
|
268
|
+
}) as any
|
|
269
|
+
|
|
270
|
+
expect(approval2.approverRole?.id).toBe(middleRole.id)
|
|
271
|
+
expect(approval2.approver?.id).toBe(middleApprover.id)
|
|
272
|
+
|
|
273
|
+
// Level 3: User 최종 승인 → Approved
|
|
274
|
+
const nextType3 = approvalLine[2].type
|
|
275
|
+
expect(nextType3).toBe('Employee')
|
|
276
|
+
|
|
277
|
+
const approval3 = await tx.save('ActivityApproval', {
|
|
278
|
+
domain,
|
|
279
|
+
activityThread: thread,
|
|
280
|
+
round: 1,
|
|
281
|
+
order: 3,
|
|
282
|
+
judgment: ActivityApprovalJudgment.Approved,
|
|
283
|
+
approver: finalUser,
|
|
284
|
+
terminatedAt: new Date(),
|
|
285
|
+
creator: finalUser,
|
|
286
|
+
updater: finalUser
|
|
287
|
+
}) as any
|
|
288
|
+
|
|
289
|
+
expect(approval3.judgment).toBe(ActivityApprovalJudgment.Approved)
|
|
290
|
+
expect(approval3.order).toBe(3)
|
|
291
|
+
})
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
describe('ROLE → ROLE → ROLE 3단계', () => {
|
|
296
|
+
it('3단계 ROLE 전용 승인라인에서 각 레벨의 roleId가 올바르게 참조되어야 한다', async () => {
|
|
297
|
+
await withTestTransaction(async (context) => {
|
|
298
|
+
const { tx } = context.state
|
|
299
|
+
const domain = await domainFactory.create({}, tx)
|
|
300
|
+
|
|
301
|
+
const role1 = await roleFactory.create({ name: 'Team Lead Role', domain }, tx)
|
|
302
|
+
const role2 = await roleFactory.create({ name: 'Manager Role', domain }, tx)
|
|
303
|
+
const role3 = await roleFactory.create({ name: 'Director Role', domain }, tx)
|
|
304
|
+
|
|
305
|
+
const approver1 = await userFactory.createWithRole({}, role1, tx)
|
|
306
|
+
const approver2 = await userFactory.createWithRole({}, role2, tx)
|
|
307
|
+
const approver3 = await userFactory.createWithRole({}, role3, tx)
|
|
308
|
+
|
|
309
|
+
// Given: ROLE → ROLE → ROLE
|
|
310
|
+
const approvalLine = [
|
|
311
|
+
createRoleApprovalLineItem(role1),
|
|
312
|
+
createRoleApprovalLineItem(role2),
|
|
313
|
+
createRoleApprovalLineItem(role3)
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
const instance = await activityInstanceFactory.createWithApprovalLine(
|
|
317
|
+
approvalLine,
|
|
318
|
+
{ state: ActivityInstanceStatus.Started },
|
|
319
|
+
undefined,
|
|
320
|
+
domain,
|
|
321
|
+
tx
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
// 버그 수정 검증: 각 에스컬레이션 시 올바른 인덱스 참조
|
|
325
|
+
// order=1 완료 후 → approvalLine[1] (role2)
|
|
326
|
+
expect(approvalLine[1].value).toBe(role2.id)
|
|
327
|
+
|
|
328
|
+
// order=2 완료 후 → approvalLine[2] (role3)
|
|
329
|
+
expect(approvalLine[2].value).toBe(role3.id)
|
|
330
|
+
|
|
331
|
+
// 수정 전 버그: 항상 approvalLine[0].value 참조 (role1.id)
|
|
332
|
+
// 이 테스트는 수정된 코드가 올바르게 작동하는지 확인
|
|
333
|
+
expect(approvalLine[0].value).not.toBe(role2.id)
|
|
334
|
+
expect(approvalLine[0].value).not.toBe(role3.id)
|
|
335
|
+
expect(approvalLine[0].value).toBe(role1.id)
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
describe('타입 분기 로직 검증', () => {
|
|
341
|
+
it('OrgMemberTargetType.Role 체크가 정확해야 한다', async () => {
|
|
342
|
+
await withTestTransaction(async (context) => {
|
|
343
|
+
const { tx } = context.state
|
|
344
|
+
const domain = await domainFactory.create({}, tx)
|
|
345
|
+
const role = await roleFactory.create({ name: 'Test Role', domain }, tx)
|
|
346
|
+
const user = await userFactory.create({ name: 'Test User' }, tx)
|
|
347
|
+
|
|
348
|
+
const roleItem = createRoleApprovalLineItem(role)
|
|
349
|
+
const userItem = createUserApprovalLineItem(user)
|
|
350
|
+
|
|
351
|
+
// Role 타입 분기
|
|
352
|
+
if (roleItem.type === 'Role') {
|
|
353
|
+
expect(roleItem.value).toBe(role.id)
|
|
354
|
+
} else {
|
|
355
|
+
fail('Should be Role type')
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Employee 타입 분기
|
|
359
|
+
if (userItem.type === 'Employee') {
|
|
360
|
+
expect(userItem.value).toBe(user.id)
|
|
361
|
+
} else {
|
|
362
|
+
fail('Should be Employee type')
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// 타입 불일치
|
|
366
|
+
expect(roleItem.type).not.toBe('Employee')
|
|
367
|
+
expect(userItem.type).not.toBe('Role')
|
|
368
|
+
})
|
|
369
|
+
})
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
describe('반려 시나리오', () => {
|
|
373
|
+
it('혼합 승인라인의 어느 레벨에서든 반려할 수 있어야 한다', async () => {
|
|
374
|
+
await withTestTransaction(async (context) => {
|
|
375
|
+
const { tx } = context.state
|
|
376
|
+
const domain = await domainFactory.create({}, tx)
|
|
377
|
+
const firstRole = await roleFactory.create({ name: 'First', domain }, tx)
|
|
378
|
+
const secondUser = await userFactory.create({ name: 'Second' }, tx)
|
|
379
|
+
const firstApprover = await userFactory.createWithRole({}, firstRole, tx)
|
|
380
|
+
|
|
381
|
+
// Given: ROLE → User 승인라인
|
|
382
|
+
const approvalLine = [
|
|
383
|
+
createRoleApprovalLineItem(firstRole),
|
|
384
|
+
createUserApprovalLineItem(secondUser)
|
|
385
|
+
]
|
|
386
|
+
|
|
387
|
+
const instance = await activityInstanceFactory.createWithApprovalLine(
|
|
388
|
+
approvalLine,
|
|
389
|
+
{ state: ActivityInstanceStatus.Started },
|
|
390
|
+
undefined,
|
|
391
|
+
domain,
|
|
392
|
+
tx
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
396
|
+
{ state: ActivityThreadStatus.Submitted },
|
|
397
|
+
instance,
|
|
398
|
+
firstApprover,
|
|
399
|
+
tx
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
// When: 첫 번째 ROLE 승인자가 반려
|
|
403
|
+
const rejection = await tx.save('ActivityApproval', {
|
|
404
|
+
domain,
|
|
405
|
+
activityThread: thread,
|
|
406
|
+
round: 1,
|
|
407
|
+
order: 1,
|
|
408
|
+
judgment: ActivityApprovalJudgment.Rejected,
|
|
409
|
+
approverRole: firstRole,
|
|
410
|
+
approver: firstApprover,
|
|
411
|
+
comment: 'Needs revision',
|
|
412
|
+
terminatedAt: new Date(),
|
|
413
|
+
creator: firstApprover,
|
|
414
|
+
updater: firstApprover
|
|
415
|
+
}) as any
|
|
416
|
+
|
|
417
|
+
// Then: Rejected 상태
|
|
418
|
+
expect(rejection.judgment).toBe(ActivityApprovalJudgment.Rejected)
|
|
419
|
+
expect(rejection.comment).toBe('Needs revision')
|
|
420
|
+
|
|
421
|
+
// 스레드도 Rejected 상태
|
|
422
|
+
thread.state = ActivityThreadStatus.Rejected
|
|
423
|
+
const rejectedThread = await tx.save('ActivityThread', thread) as any
|
|
424
|
+
expect(rejectedThread.state).toBe(ActivityThreadStatus.Rejected)
|
|
425
|
+
})
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it('반려 후 재시작 시 round가 증가해야 한다', async () => {
|
|
429
|
+
await withTestTransaction(async (context) => {
|
|
430
|
+
const { tx } = context.state
|
|
431
|
+
const domain = await domainFactory.create({}, tx)
|
|
432
|
+
const approverRole = await roleFactory.create({ name: 'Approver', domain }, tx)
|
|
433
|
+
const user = await userFactory.create({}, tx)
|
|
434
|
+
|
|
435
|
+
// Given: round=1인 스레드
|
|
436
|
+
const instance = await activityInstanceFactory.createWithActivity(
|
|
437
|
+
{ state: ActivityInstanceStatus.Started },
|
|
438
|
+
undefined,
|
|
439
|
+
domain,
|
|
440
|
+
tx
|
|
441
|
+
)
|
|
442
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
443
|
+
{ state: ActivityThreadStatus.Rejected, round: 1 },
|
|
444
|
+
instance,
|
|
445
|
+
user,
|
|
446
|
+
tx
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
expect(thread.round).toBe(1)
|
|
450
|
+
|
|
451
|
+
// When: 재시작 시 round 증가
|
|
452
|
+
const newRound = thread.round + 1
|
|
453
|
+
thread.round = newRound
|
|
454
|
+
thread.state = ActivityThreadStatus.Started
|
|
455
|
+
const restarted = await tx.save('ActivityThread', thread) as any
|
|
456
|
+
|
|
457
|
+
// Then
|
|
458
|
+
expect(restarted.round).toBe(2)
|
|
459
|
+
expect(restarted.state).toBe(ActivityThreadStatus.Started)
|
|
460
|
+
})
|
|
461
|
+
})
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
describe('createMultiLevelApprovalLine 헬퍼 테스트', () => {
|
|
465
|
+
it('User와 Role 혼합 배열에서 올바른 타입으로 생성되어야 한다', async () => {
|
|
466
|
+
await withTestTransaction(async (context) => {
|
|
467
|
+
const { tx } = context.state
|
|
468
|
+
const domain = await domainFactory.create({}, tx)
|
|
469
|
+
|
|
470
|
+
const user1 = await userFactory.create({ name: 'User 1' }, tx)
|
|
471
|
+
const role1 = await roleFactory.create({ name: 'Role 1', domain }, tx)
|
|
472
|
+
const user2 = await userFactory.create({ name: 'User 2' }, tx)
|
|
473
|
+
|
|
474
|
+
// When: 혼합 배열로 승인라인 생성
|
|
475
|
+
const approvalLine = createMultiLevelApprovalLine([user1, role1, user2])
|
|
476
|
+
|
|
477
|
+
// Then: 타입이 올바르게 설정됨
|
|
478
|
+
expect(approvalLine.length).toBe(3)
|
|
479
|
+
|
|
480
|
+
expect(approvalLine[0].type).toBe('Employee')
|
|
481
|
+
expect(approvalLine[0].value).toBe(user1.id)
|
|
482
|
+
|
|
483
|
+
expect(approvalLine[1].type).toBe('Role')
|
|
484
|
+
expect(approvalLine[1].value).toBe(role1.id)
|
|
485
|
+
|
|
486
|
+
expect(approvalLine[2].type).toBe('Employee')
|
|
487
|
+
expect(approvalLine[2].value).toBe(user2.id)
|
|
488
|
+
})
|
|
489
|
+
})
|
|
490
|
+
})
|
|
491
|
+
})
|