@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
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ROLE Approval Submit Logic Tests
|
|
3
|
+
* ROLE 타입 승인라인 Submit 로직 검증 테스트
|
|
4
|
+
*
|
|
5
|
+
* 수정된 버그:
|
|
6
|
+
* - submit.ts:60 - if (approverRoleId) → if (!approverRoleId)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { TestDatabase } from '../../../../test/test-database'
|
|
10
|
+
import { withTestTransaction } from '../../../../test/test-context'
|
|
11
|
+
import {
|
|
12
|
+
domainFactory,
|
|
13
|
+
userFactory,
|
|
14
|
+
roleFactory,
|
|
15
|
+
activityFactory,
|
|
16
|
+
activityInstanceFactory,
|
|
17
|
+
activityThreadFactory,
|
|
18
|
+
activityApprovalFactory
|
|
19
|
+
} from '../../../../test/factories'
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
ActivityInstanceStatus,
|
|
23
|
+
ActivityThreadStatus,
|
|
24
|
+
ActivityApprovalJudgment
|
|
25
|
+
} from '../../../../test/entities/schemas'
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
createRoleApprovalLineItem,
|
|
29
|
+
createUserApprovalLineItem,
|
|
30
|
+
createMultiLevelApprovalLine
|
|
31
|
+
} from '../../../../test/helpers/workflow-helpers'
|
|
32
|
+
|
|
33
|
+
describe('ROLE Approval Submit Logic Tests', () => {
|
|
34
|
+
let testDb: TestDatabase
|
|
35
|
+
|
|
36
|
+
beforeAll(async () => {
|
|
37
|
+
testDb = TestDatabase.getInstance()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('단일 ROLE 타입 승인라인 Submit', () => {
|
|
41
|
+
it('ROLE 타입 승인라인이 있는 스레드를 submit하면 ActivityApproval이 생성되어야 한다', async () => {
|
|
42
|
+
await withTestTransaction(async (context) => {
|
|
43
|
+
const { tx } = context.state
|
|
44
|
+
const domain = await domainFactory.create({}, tx)
|
|
45
|
+
const user = await userFactory.create({}, tx)
|
|
46
|
+
const approverRole = await roleFactory.create({ name: 'Approver Role', domain }, tx)
|
|
47
|
+
|
|
48
|
+
// Given: ROLE 타입 승인라인을 가진 ActivityInstance
|
|
49
|
+
const approvalLine = [createRoleApprovalLineItem(approverRole)]
|
|
50
|
+
const instance = await activityInstanceFactory.createWithApprovalLine(
|
|
51
|
+
approvalLine,
|
|
52
|
+
{ state: ActivityInstanceStatus.Started },
|
|
53
|
+
undefined,
|
|
54
|
+
domain,
|
|
55
|
+
tx
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
// Given: Started 상태의 스레드
|
|
59
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
60
|
+
{ state: ActivityThreadStatus.Started },
|
|
61
|
+
instance,
|
|
62
|
+
user,
|
|
63
|
+
tx
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// When: Submit 시뮬레이션 - ActivityApproval 생성
|
|
67
|
+
const approval = await tx.save('ActivityApproval', {
|
|
68
|
+
domain,
|
|
69
|
+
activityThread: thread,
|
|
70
|
+
round: thread.round,
|
|
71
|
+
order: 1,
|
|
72
|
+
judgment: ActivityApprovalJudgment.Pending,
|
|
73
|
+
transaction: 'submit',
|
|
74
|
+
approverRole: approverRole,
|
|
75
|
+
creator: user,
|
|
76
|
+
updater: user
|
|
77
|
+
}) as any
|
|
78
|
+
|
|
79
|
+
// Then: ActivityApproval이 approverRole과 함께 생성됨
|
|
80
|
+
expect(approval).toBeDefined()
|
|
81
|
+
expect(approval.approverRole?.id).toBe(approverRole.id)
|
|
82
|
+
expect(approval.approver).toBeUndefined()
|
|
83
|
+
expect(approval.judgment).toBe(ActivityApprovalJudgment.Pending)
|
|
84
|
+
expect(approval.order).toBe(1)
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('ROLE 타입 approval의 value(roleId)가 반드시 존재해야 한다', async () => {
|
|
89
|
+
await withTestTransaction(async (context) => {
|
|
90
|
+
const { tx } = context.state
|
|
91
|
+
const domain = await domainFactory.create({}, tx)
|
|
92
|
+
const approverRole = await roleFactory.create({ name: 'Valid Role', domain }, tx)
|
|
93
|
+
|
|
94
|
+
// Given: 유효한 ROLE 승인라인
|
|
95
|
+
const approvalLineItem = createRoleApprovalLineItem(approverRole)
|
|
96
|
+
|
|
97
|
+
// Then: value(roleId)가 존재해야 함
|
|
98
|
+
expect(approvalLineItem.value).toBeDefined()
|
|
99
|
+
expect(approvalLineItem.value).toBe(approverRole.id)
|
|
100
|
+
expect(approvalLineItem.type).toBe('Role')
|
|
101
|
+
|
|
102
|
+
// 버그 수정 확인: value가 있으면 정상 처리되어야 함
|
|
103
|
+
// 수정 전: if (approverRoleId) throw - 항상 에러
|
|
104
|
+
// 수정 후: if (!approverRoleId) throw - roleId 없을 때만 에러
|
|
105
|
+
const hasValidRoleId = !!approvalLineItem.value
|
|
106
|
+
expect(hasValidRoleId).toBe(true)
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('ROLE 타입 승인라인의 value가 없으면 오류가 발생해야 한다', async () => {
|
|
111
|
+
await withTestTransaction(async (context) => {
|
|
112
|
+
const { tx } = context.state
|
|
113
|
+
|
|
114
|
+
// Given: value가 없는 잘못된 ROLE 승인라인
|
|
115
|
+
const invalidApprovalLineItem = {
|
|
116
|
+
type: 'Role',
|
|
117
|
+
value: undefined, // roleId가 없음
|
|
118
|
+
approver: { id: undefined, name: 'Invalid' }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Then: value가 없으면 에러 조건 충족
|
|
122
|
+
const approverRoleId = invalidApprovalLineItem.value
|
|
123
|
+
const shouldThrowError = !approverRoleId
|
|
124
|
+
expect(shouldThrowError).toBe(true)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('빈 문자열 roleId도 오류로 처리되어야 한다', async () => {
|
|
129
|
+
await withTestTransaction(async (context) => {
|
|
130
|
+
const { tx } = context.state
|
|
131
|
+
|
|
132
|
+
// Given: 빈 문자열 value
|
|
133
|
+
const invalidApprovalLineItem = {
|
|
134
|
+
type: 'Role',
|
|
135
|
+
value: '', // 빈 문자열
|
|
136
|
+
approver: { id: '', name: 'Invalid' }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Then: 빈 문자열도 falsy이므로 오류 조건 충족
|
|
140
|
+
const approverRoleId = invalidApprovalLineItem.value
|
|
141
|
+
const shouldThrowError = !approverRoleId
|
|
142
|
+
expect(shouldThrowError).toBe(true)
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
describe('User 타입 vs ROLE 타입 승인라인 비교', () => {
|
|
148
|
+
it('User 타입 승인라인은 approver가 직접 설정되어야 한다', async () => {
|
|
149
|
+
await withTestTransaction(async (context) => {
|
|
150
|
+
const { tx } = context.state
|
|
151
|
+
const domain = await domainFactory.create({}, tx)
|
|
152
|
+
const user = await userFactory.create({}, tx)
|
|
153
|
+
const approverUser = await userFactory.create({ name: 'Approver' }, tx)
|
|
154
|
+
|
|
155
|
+
// Given: User 타입 승인라인
|
|
156
|
+
const approvalLineItem = createUserApprovalLineItem(approverUser)
|
|
157
|
+
expect(approvalLineItem.type).toBe('Employee')
|
|
158
|
+
|
|
159
|
+
// When: ActivityApproval 생성
|
|
160
|
+
const instance = await activityInstanceFactory.createWithApprovalLine(
|
|
161
|
+
[approvalLineItem],
|
|
162
|
+
{ state: ActivityInstanceStatus.Started },
|
|
163
|
+
undefined,
|
|
164
|
+
domain,
|
|
165
|
+
tx
|
|
166
|
+
)
|
|
167
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
168
|
+
{ state: ActivityThreadStatus.Started },
|
|
169
|
+
instance,
|
|
170
|
+
user,
|
|
171
|
+
tx
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
const approval = await tx.save('ActivityApproval', {
|
|
175
|
+
domain,
|
|
176
|
+
activityThread: thread,
|
|
177
|
+
round: 1,
|
|
178
|
+
order: 1,
|
|
179
|
+
judgment: ActivityApprovalJudgment.Pending,
|
|
180
|
+
transaction: 'submit',
|
|
181
|
+
approver: approverUser,
|
|
182
|
+
creator: user,
|
|
183
|
+
updater: user
|
|
184
|
+
}) as any
|
|
185
|
+
|
|
186
|
+
// Then: approver가 설정되고 approverRole은 없음
|
|
187
|
+
expect(approval.approver?.id).toBe(approverUser.id)
|
|
188
|
+
expect(approval.approverRole).toBeUndefined()
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('ROLE 타입 승인라인은 approverRole이 설정되고 approver는 나중에 할당된다', async () => {
|
|
193
|
+
await withTestTransaction(async (context) => {
|
|
194
|
+
const { tx } = context.state
|
|
195
|
+
const domain = await domainFactory.create({}, tx)
|
|
196
|
+
const user = await userFactory.create({}, tx)
|
|
197
|
+
const approverRole = await roleFactory.create({ name: 'Approver Role', domain }, tx)
|
|
198
|
+
|
|
199
|
+
// Given: ROLE 타입 승인라인
|
|
200
|
+
const approvalLineItem = createRoleApprovalLineItem(approverRole)
|
|
201
|
+
expect(approvalLineItem.type).toBe('Role')
|
|
202
|
+
|
|
203
|
+
// When: ActivityApproval 생성 (submit 시점)
|
|
204
|
+
const instance = await activityInstanceFactory.createWithApprovalLine(
|
|
205
|
+
[approvalLineItem],
|
|
206
|
+
{ state: ActivityInstanceStatus.Started },
|
|
207
|
+
undefined,
|
|
208
|
+
domain,
|
|
209
|
+
tx
|
|
210
|
+
)
|
|
211
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
212
|
+
{ state: ActivityThreadStatus.Started },
|
|
213
|
+
instance,
|
|
214
|
+
user,
|
|
215
|
+
tx
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
const approval = await tx.save('ActivityApproval', {
|
|
219
|
+
domain,
|
|
220
|
+
activityThread: thread,
|
|
221
|
+
round: 1,
|
|
222
|
+
order: 1,
|
|
223
|
+
judgment: ActivityApprovalJudgment.Pending,
|
|
224
|
+
transaction: 'submit',
|
|
225
|
+
approverRole: approverRole,
|
|
226
|
+
creator: user,
|
|
227
|
+
updater: user
|
|
228
|
+
}) as any
|
|
229
|
+
|
|
230
|
+
// Then: approverRole이 설정되고 approver는 없음
|
|
231
|
+
expect(approval.approverRole?.id).toBe(approverRole.id)
|
|
232
|
+
expect(approval.approver).toBeUndefined()
|
|
233
|
+
|
|
234
|
+
// When: 역할 소유자가 승인 시 approver 할당
|
|
235
|
+
const roleOwner = await userFactory.createWithRole({}, approverRole, tx)
|
|
236
|
+
approval.approver = roleOwner
|
|
237
|
+
approval.judgment = ActivityApprovalJudgment.Approved
|
|
238
|
+
approval.terminatedAt = new Date()
|
|
239
|
+
|
|
240
|
+
const approved = await tx.save('ActivityApproval', approval) as any
|
|
241
|
+
|
|
242
|
+
// Then: 이제 approver도 설정됨
|
|
243
|
+
expect(approved.approver?.id).toBe(roleOwner.id)
|
|
244
|
+
expect(approved.approverRole?.id).toBe(approverRole.id)
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
describe('Submit 후 스레드 상태 전이', () => {
|
|
250
|
+
it('승인라인이 있으면 스레드는 Submitted 상태가 되어야 한다', async () => {
|
|
251
|
+
await withTestTransaction(async (context) => {
|
|
252
|
+
const { tx } = context.state
|
|
253
|
+
const domain = await domainFactory.create({}, tx)
|
|
254
|
+
const user = await userFactory.create({}, tx)
|
|
255
|
+
const approverRole = await roleFactory.create({ name: 'Approver', domain }, tx)
|
|
256
|
+
|
|
257
|
+
// Given: ROLE 승인라인을 가진 Instance
|
|
258
|
+
const approvalLine = [createRoleApprovalLineItem(approverRole)]
|
|
259
|
+
const instance = await activityInstanceFactory.createWithApprovalLine(
|
|
260
|
+
approvalLine,
|
|
261
|
+
{ state: ActivityInstanceStatus.Started },
|
|
262
|
+
undefined,
|
|
263
|
+
domain,
|
|
264
|
+
tx
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
// Given: Started 상태 스레드
|
|
268
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
269
|
+
{ state: ActivityThreadStatus.Started },
|
|
270
|
+
instance,
|
|
271
|
+
user,
|
|
272
|
+
tx
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
// When: Submit 후 상태 변경
|
|
276
|
+
thread.state = ActivityThreadStatus.Submitted
|
|
277
|
+
const submitted = await tx.save('ActivityThread', thread) as any
|
|
278
|
+
|
|
279
|
+
// Then
|
|
280
|
+
expect(submitted.state).toBe(ActivityThreadStatus.Submitted)
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('승인라인이 없으면 스레드는 Ended 상태가 되어야 한다', async () => {
|
|
285
|
+
await withTestTransaction(async (context) => {
|
|
286
|
+
const { tx } = context.state
|
|
287
|
+
const domain = await domainFactory.create({}, tx)
|
|
288
|
+
const user = await userFactory.create({}, tx)
|
|
289
|
+
|
|
290
|
+
// Given: 승인라인이 없는 Instance
|
|
291
|
+
const instance = await activityInstanceFactory.createWithActivity(
|
|
292
|
+
{ state: ActivityInstanceStatus.Started },
|
|
293
|
+
undefined,
|
|
294
|
+
domain,
|
|
295
|
+
tx
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
// Given: Started 상태 스레드
|
|
299
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
300
|
+
{ state: ActivityThreadStatus.Started },
|
|
301
|
+
instance,
|
|
302
|
+
user,
|
|
303
|
+
tx
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
// When: Submit 후 상태 변경 (승인라인 없음)
|
|
307
|
+
thread.state = ActivityThreadStatus.Ended
|
|
308
|
+
thread.terminatedAt = new Date()
|
|
309
|
+
const ended = await tx.save('ActivityThread', thread) as any
|
|
310
|
+
|
|
311
|
+
// Then
|
|
312
|
+
expect(ended.state).toBe(ActivityThreadStatus.Ended)
|
|
313
|
+
expect(ended.terminatedAt).toBeDefined()
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
describe('Submit 전 상태 검증', () => {
|
|
319
|
+
it('Assigned 상태에서도 Submit이 가능해야 한다 (암묵적 시작)', async () => {
|
|
320
|
+
await withTestTransaction(async (context) => {
|
|
321
|
+
const { tx } = context.state
|
|
322
|
+
const domain = await domainFactory.create({}, tx)
|
|
323
|
+
const user = await userFactory.create({}, tx)
|
|
324
|
+
const approverRole = await roleFactory.create({ name: 'Approver', domain }, tx)
|
|
325
|
+
|
|
326
|
+
// Given: Assigned 상태 스레드
|
|
327
|
+
const approvalLine = [createRoleApprovalLineItem(approverRole)]
|
|
328
|
+
const instance = await activityInstanceFactory.createWithApprovalLine(
|
|
329
|
+
approvalLine,
|
|
330
|
+
{ state: ActivityInstanceStatus.Assigned },
|
|
331
|
+
undefined,
|
|
332
|
+
domain,
|
|
333
|
+
tx
|
|
334
|
+
)
|
|
335
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
336
|
+
{ state: ActivityThreadStatus.Assigned },
|
|
337
|
+
instance,
|
|
338
|
+
user,
|
|
339
|
+
tx
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
// Then: Assigned와 Started 둘 다 submit 가능한 상태
|
|
343
|
+
const canSubmit =
|
|
344
|
+
thread.state === ActivityThreadStatus.Assigned ||
|
|
345
|
+
thread.state === ActivityThreadStatus.Started
|
|
346
|
+
expect(canSubmit).toBe(true)
|
|
347
|
+
})
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('Ended 상태에서는 Submit이 불가능해야 한다', async () => {
|
|
351
|
+
await withTestTransaction(async (context) => {
|
|
352
|
+
const { tx } = context.state
|
|
353
|
+
const domain = await domainFactory.create({}, tx)
|
|
354
|
+
const user = await userFactory.create({}, tx)
|
|
355
|
+
|
|
356
|
+
// Given: Ended 상태 스레드
|
|
357
|
+
const instance = await activityInstanceFactory.createWithActivity(
|
|
358
|
+
{ state: ActivityInstanceStatus.Ended },
|
|
359
|
+
undefined,
|
|
360
|
+
domain,
|
|
361
|
+
tx
|
|
362
|
+
)
|
|
363
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
364
|
+
{
|
|
365
|
+
state: ActivityThreadStatus.Ended,
|
|
366
|
+
terminatedAt: new Date()
|
|
367
|
+
},
|
|
368
|
+
instance,
|
|
369
|
+
user,
|
|
370
|
+
tx
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
// Then: Submit 불가능
|
|
374
|
+
const canSubmit =
|
|
375
|
+
thread.state === ActivityThreadStatus.Assigned ||
|
|
376
|
+
thread.state === ActivityThreadStatus.Started
|
|
377
|
+
expect(canSubmit).toBe(false)
|
|
378
|
+
})
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
it('Aborted 상태에서는 Submit이 불가능해야 한다', async () => {
|
|
382
|
+
await withTestTransaction(async (context) => {
|
|
383
|
+
const { tx } = context.state
|
|
384
|
+
const domain = await domainFactory.create({}, tx)
|
|
385
|
+
const user = await userFactory.create({}, tx)
|
|
386
|
+
|
|
387
|
+
// Given: Aborted 상태 스레드
|
|
388
|
+
const instance = await activityInstanceFactory.createWithActivity(
|
|
389
|
+
{ state: ActivityInstanceStatus.Aborted },
|
|
390
|
+
undefined,
|
|
391
|
+
domain,
|
|
392
|
+
tx
|
|
393
|
+
)
|
|
394
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
395
|
+
{
|
|
396
|
+
state: ActivityThreadStatus.Aborted,
|
|
397
|
+
terminatedAt: new Date()
|
|
398
|
+
},
|
|
399
|
+
instance,
|
|
400
|
+
user,
|
|
401
|
+
tx
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
// Then: Submit 불가능
|
|
405
|
+
const canSubmit =
|
|
406
|
+
thread.state === ActivityThreadStatus.Assigned ||
|
|
407
|
+
thread.state === ActivityThreadStatus.Started
|
|
408
|
+
expect(canSubmit).toBe(false)
|
|
409
|
+
})
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
describe('startedAt 자동 설정', () => {
|
|
414
|
+
it('Submit 시 startedAt이 없으면 자동 설정되어야 한다', async () => {
|
|
415
|
+
await withTestTransaction(async (context) => {
|
|
416
|
+
const { tx } = context.state
|
|
417
|
+
const domain = await domainFactory.create({}, tx)
|
|
418
|
+
const user = await userFactory.create({}, tx)
|
|
419
|
+
|
|
420
|
+
// Given: startedAt이 없는 Assigned 스레드
|
|
421
|
+
const instance = await activityInstanceFactory.createWithActivity(
|
|
422
|
+
{ state: ActivityInstanceStatus.Assigned },
|
|
423
|
+
undefined,
|
|
424
|
+
domain,
|
|
425
|
+
tx
|
|
426
|
+
)
|
|
427
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
428
|
+
{ state: ActivityThreadStatus.Assigned },
|
|
429
|
+
instance,
|
|
430
|
+
user,
|
|
431
|
+
tx
|
|
432
|
+
)
|
|
433
|
+
expect(thread.startedAt).toBeFalsy() // null or undefined
|
|
434
|
+
|
|
435
|
+
// When: Submit 시 startedAt 설정
|
|
436
|
+
if (!thread.startedAt) {
|
|
437
|
+
thread.startedAt = new Date()
|
|
438
|
+
}
|
|
439
|
+
const updated = await tx.save('ActivityThread', thread) as any
|
|
440
|
+
|
|
441
|
+
// Then
|
|
442
|
+
expect(updated.startedAt).toBeDefined()
|
|
443
|
+
expect(updated.startedAt).toBeInstanceOf(Date)
|
|
444
|
+
})
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
it('Submit 시 startedAt이 이미 있으면 유지되어야 한다', async () => {
|
|
448
|
+
await withTestTransaction(async (context) => {
|
|
449
|
+
const { tx } = context.state
|
|
450
|
+
const domain = await domainFactory.create({}, tx)
|
|
451
|
+
const user = await userFactory.create({}, tx)
|
|
452
|
+
|
|
453
|
+
// Given: startedAt이 이미 있는 Started 스레드
|
|
454
|
+
const originalStartedAt = new Date('2024-01-01T00:00:00Z')
|
|
455
|
+
const instance = await activityInstanceFactory.createWithActivity(
|
|
456
|
+
{ state: ActivityInstanceStatus.Started },
|
|
457
|
+
undefined,
|
|
458
|
+
domain,
|
|
459
|
+
tx
|
|
460
|
+
)
|
|
461
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
462
|
+
{
|
|
463
|
+
state: ActivityThreadStatus.Started,
|
|
464
|
+
startedAt: originalStartedAt
|
|
465
|
+
},
|
|
466
|
+
instance,
|
|
467
|
+
user,
|
|
468
|
+
tx
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
// When: Submit 시 startedAt 확인
|
|
472
|
+
if (!thread.startedAt) {
|
|
473
|
+
thread.startedAt = new Date()
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Then: 기존 startedAt 유지
|
|
477
|
+
expect(thread.startedAt).toEqual(originalStartedAt)
|
|
478
|
+
})
|
|
479
|
+
})
|
|
480
|
+
})
|
|
481
|
+
})
|