@things-factory/organization 9.1.19 → 9.2.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/organization",
3
- "version": "9.1.19",
3
+ "version": "9.2.13",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -33,10 +33,10 @@
33
33
  "@operato/graphql": "^9.0.0",
34
34
  "@operato/p13n": "^9.0.0",
35
35
  "@operato/shell": "^9.0.0",
36
- "@things-factory/auth-base": "^9.1.19",
37
- "@things-factory/code-ui": "^9.1.19",
38
- "@things-factory/contact": "^9.1.19",
39
- "@things-factory/shell": "^9.1.19"
36
+ "@things-factory/auth-base": "^9.2.13",
37
+ "@things-factory/code-ui": "^9.2.13",
38
+ "@things-factory/contact": "^9.2.13",
39
+ "@things-factory/shell": "^9.2.13"
40
40
  },
41
- "gitHead": "078438034dbe19915108e89ff24024f7044a85a9"
41
+ "gitHead": "d65748803a86a1fb6c9810ea4f93519c6f44f6d5"
42
42
  }
@@ -0,0 +1,329 @@
1
+ /**
2
+ * ApprovalLine Unit 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
+ employeeFactory,
13
+ approvalLineFactory
14
+ } from '../../../../test/factories'
15
+ import { ApprovalLineOwnerType } from '../../../../test/entities/schemas'
16
+
17
+ describe('ApprovalLine Entity Tests', () => {
18
+ let testDb: TestDatabase
19
+
20
+ beforeAll(async () => {
21
+ testDb = TestDatabase.getInstance()
22
+ })
23
+
24
+ describe('ApprovalLine 생성', () => {
25
+ it('ApprovalLine이 올바르게 생성되어야 한다', async () => {
26
+ await withTestTransaction(async (context) => {
27
+ const { tx, domain } = context.state
28
+
29
+ // When
30
+ const approvalLine = await approvalLineFactory.createWithDomain(
31
+ {
32
+ name: 'Standard Approval',
33
+ description: 'Standard approval process',
34
+ ownerType: ApprovalLineOwnerType.Common
35
+ },
36
+ domain,
37
+ tx
38
+ )
39
+
40
+ // Then
41
+ expect(approvalLine).toBeDefined()
42
+ expect(approvalLine.id).toBeDefined()
43
+ expect(approvalLine.name).toBe('Standard Approval')
44
+ expect(approvalLine.description).toBe('Standard approval process')
45
+ expect(approvalLine.ownerType).toBe(ApprovalLineOwnerType.Common)
46
+ expect(approvalLine.domain?.id).toBe(domain.id)
47
+ })
48
+ })
49
+
50
+ it('Employee 소유의 ApprovalLine이 생성될 수 있어야 한다', async () => {
51
+ await withTestTransaction(async (context) => {
52
+ const { tx, domain } = context.state
53
+
54
+ // Given
55
+ const employee = await employeeFactory.createWithDomain({}, domain, tx)
56
+
57
+ // When
58
+ const approvalLine = await approvalLineFactory.createForEmployee(
59
+ { name: 'My Approval Line' },
60
+ employee,
61
+ tx
62
+ )
63
+
64
+ // Then
65
+ expect(approvalLine.ownerType).toBe(ApprovalLineOwnerType.Employee)
66
+ expect(approvalLine.ownerValue).toBe(employee.id)
67
+ expect(approvalLine.ownerEmployee?.id).toBe(employee.id)
68
+ })
69
+ })
70
+ })
71
+
72
+ describe('ApprovalLine Model (결재 단계)', () => {
73
+ it('Role 타입 결재 아이템이 생성되어야 한다', async () => {
74
+ await withTestTransaction(async (context) => {
75
+ const { tx, domain } = context.state
76
+
77
+ // Given
78
+ const role = await roleFactory.create({ name: 'Approver Role', domain }, tx)
79
+
80
+ // When
81
+ const item = approvalLineFactory.createRoleApprovalItem(role)
82
+
83
+ // Then
84
+ expect(item.type).toBe('Role')
85
+ expect(item.value).toBe(role.id)
86
+ expect(item.approver?.id).toBe(role.id)
87
+ expect(item.approver?.name).toBe('Approver Role')
88
+ })
89
+ })
90
+
91
+ it('Employee 타입 결재 아이템이 생성되어야 한다', async () => {
92
+ await withTestTransaction(async (context) => {
93
+ const { tx, domain } = context.state
94
+
95
+ // Given
96
+ const employee = await employeeFactory.createWithDomain(
97
+ { name: 'Approver Employee' },
98
+ domain,
99
+ tx
100
+ )
101
+
102
+ // When
103
+ const item = approvalLineFactory.createEmployeeApprovalItem(employee)
104
+
105
+ // Then
106
+ expect(item.type).toBe('Employee')
107
+ expect(item.value).toBe(employee.id)
108
+ expect(item.approver?.id).toBe(employee.id)
109
+ expect(item.approver?.name).toBe('Approver Employee')
110
+ })
111
+ })
112
+
113
+ it('Supervisor 타입 결재 아이템이 생성되어야 한다', async () => {
114
+ // When
115
+ const item = approvalLineFactory.createSupervisorApprovalItem()
116
+
117
+ // Then
118
+ expect(item.type).toBe('Supervisor')
119
+ expect(item.value).toBeUndefined()
120
+ })
121
+
122
+ it('DepartmentManager 타입 결재 아이템이 생성되어야 한다', async () => {
123
+ // When
124
+ const item = approvalLineFactory.createDepartmentManagerApprovalItem()
125
+
126
+ // Then
127
+ expect(item.type).toBe('DepartmentManager')
128
+ expect(item.value).toBeUndefined()
129
+ })
130
+ })
131
+
132
+ describe('단일 레벨 결재라인', () => {
133
+ it('Role 기반 단일 레벨 결재라인이 생성되어야 한다', async () => {
134
+ await withTestTransaction(async (context) => {
135
+ const { tx, domain } = context.state
136
+
137
+ // Given
138
+ const role = await roleFactory.create({ name: 'Approver', domain }, tx)
139
+
140
+ // When
141
+ const approvalLine = await approvalLineFactory.createSingleRoleApprovalLine(
142
+ role,
143
+ domain,
144
+ tx
145
+ )
146
+
147
+ // Then
148
+ expect(approvalLine.model).toBeDefined()
149
+ expect(approvalLine.model?.length).toBe(1)
150
+ expect(approvalLine.model?.[0].type).toBe('Role')
151
+ expect(approvalLine.model?.[0].approver?.id).toBe(role.id)
152
+ })
153
+ })
154
+ })
155
+
156
+ describe('다중 레벨 결재라인', () => {
157
+ it('여러 단계의 결재라인이 생성되어야 한다', async () => {
158
+ await withTestTransaction(async (context) => {
159
+ const { tx, domain } = context.state
160
+
161
+ // Given
162
+ const teamLeadRole = await roleFactory.create({ name: 'Team Lead', domain }, tx)
163
+ const managerRole = await roleFactory.create({ name: 'Manager', domain }, tx)
164
+ const directorRole = await roleFactory.create({ name: 'Director', domain }, tx)
165
+
166
+ const items = [
167
+ approvalLineFactory.createRoleApprovalItem(teamLeadRole),
168
+ approvalLineFactory.createRoleApprovalItem(managerRole),
169
+ approvalLineFactory.createRoleApprovalItem(directorRole)
170
+ ]
171
+
172
+ // When
173
+ const approvalLine = await approvalLineFactory.createMultiLevelApprovalLine(
174
+ items,
175
+ domain,
176
+ tx
177
+ )
178
+
179
+ // Then
180
+ expect(approvalLine.model?.length).toBe(3)
181
+ expect(approvalLine.model?.[0].approver?.name).toBe('Team Lead')
182
+ expect(approvalLine.model?.[1].approver?.name).toBe('Manager')
183
+ expect(approvalLine.model?.[2].approver?.name).toBe('Director')
184
+ })
185
+ })
186
+
187
+ it('표준 2단계 결재라인이 생성되어야 한다', async () => {
188
+ await withTestTransaction(async (context) => {
189
+ const { tx, domain } = context.state
190
+
191
+ // When
192
+ const { approvalLine, teamLeadRole, managerRole } =
193
+ await approvalLineFactory.createStandardApprovalLine(domain, tx)
194
+
195
+ // Then
196
+ expect(approvalLine.name).toBe('Standard 2-Level Approval')
197
+ expect(approvalLine.model?.length).toBe(2)
198
+ expect(approvalLine.model?.[0].approver?.id).toBe(teamLeadRole.id)
199
+ expect(approvalLine.model?.[1].approver?.id).toBe(managerRole.id)
200
+ })
201
+ })
202
+
203
+ it('혼합 타입 결재라인이 생성되어야 한다 (Role + Employee)', async () => {
204
+ await withTestTransaction(async (context) => {
205
+ const { tx, domain } = context.state
206
+
207
+ // Given
208
+ const role = await roleFactory.create({ name: 'Team Lead', domain }, tx)
209
+ const employee = await employeeFactory.createWithDomain(
210
+ { name: 'Final Approver' },
211
+ domain,
212
+ tx
213
+ )
214
+
215
+ const items = [
216
+ approvalLineFactory.createRoleApprovalItem(role),
217
+ approvalLineFactory.createEmployeeApprovalItem(employee)
218
+ ]
219
+
220
+ // When
221
+ const approvalLine = await approvalLineFactory.createMultiLevelApprovalLine(
222
+ items,
223
+ domain,
224
+ tx
225
+ )
226
+
227
+ // Then
228
+ expect(approvalLine.model?.length).toBe(2)
229
+ expect(approvalLine.model?.[0].type).toBe('Role')
230
+ expect(approvalLine.model?.[1].type).toBe('Employee')
231
+ })
232
+ })
233
+
234
+ it('동적 타입 결재라인이 생성되어야 한다 (Supervisor + DepartmentManager)', async () => {
235
+ await withTestTransaction(async (context) => {
236
+ const { tx, domain } = context.state
237
+
238
+ // Given
239
+ const items = [
240
+ approvalLineFactory.createSupervisorApprovalItem(),
241
+ approvalLineFactory.createDepartmentManagerApprovalItem()
242
+ ]
243
+
244
+ // When
245
+ const approvalLine = await approvalLineFactory.createMultiLevelApprovalLine(
246
+ items,
247
+ domain,
248
+ tx
249
+ )
250
+
251
+ // Then
252
+ expect(approvalLine.model?.length).toBe(2)
253
+ expect(approvalLine.model?.[0].type).toBe('Supervisor')
254
+ expect(approvalLine.model?.[1].type).toBe('DepartmentManager')
255
+ })
256
+ })
257
+ })
258
+
259
+ describe('ApprovalLine 조회', () => {
260
+ it('Domain으로 ApprovalLine을 조회할 수 있어야 한다', async () => {
261
+ await withTestTransaction(async (context) => {
262
+ const { tx, domain } = context.state
263
+
264
+ // Given
265
+ await approvalLineFactory.createWithDomain({ name: 'Line A' }, domain, tx)
266
+ await approvalLineFactory.createWithDomain({ name: 'Line B' }, domain, tx)
267
+
268
+ // When
269
+ const approvalLines = await tx.getRepository('ApprovalLine').find({
270
+ where: { domain: { id: domain.id } }
271
+ })
272
+
273
+ // Then
274
+ expect(approvalLines.length).toBe(2)
275
+ })
276
+ })
277
+
278
+ it('Common 타입 ApprovalLine만 조회할 수 있어야 한다', async () => {
279
+ await withTestTransaction(async (context) => {
280
+ const { tx, domain } = context.state
281
+
282
+ // Given
283
+ await approvalLineFactory.createWithDomain(
284
+ { ownerType: ApprovalLineOwnerType.Common },
285
+ domain,
286
+ tx
287
+ )
288
+ const employee = await employeeFactory.createWithDomain({}, domain, tx)
289
+ await approvalLineFactory.createForEmployee({}, employee, tx)
290
+
291
+ // When
292
+ const commonLines = await tx.getRepository('ApprovalLine').find({
293
+ where: {
294
+ domain: { id: domain.id },
295
+ ownerType: ApprovalLineOwnerType.Common
296
+ }
297
+ })
298
+
299
+ // Then
300
+ expect(commonLines.length).toBe(1)
301
+ expect(commonLines[0].ownerType).toBe(ApprovalLineOwnerType.Common)
302
+ })
303
+ })
304
+
305
+ it('특정 Employee의 ApprovalLine만 조회할 수 있어야 한다', async () => {
306
+ await withTestTransaction(async (context) => {
307
+ const { tx, domain } = context.state
308
+
309
+ // Given
310
+ const employee1 = await employeeFactory.createWithDomain({ name: 'Emp 1' }, domain, tx)
311
+ const employee2 = await employeeFactory.createWithDomain({ name: 'Emp 2' }, domain, tx)
312
+
313
+ await approvalLineFactory.createForEmployee({ name: 'Line 1' }, employee1, tx)
314
+ await approvalLineFactory.createForEmployee({ name: 'Line 2' }, employee2, tx)
315
+
316
+ // When
317
+ const employee1Lines = await tx.getRepository('ApprovalLine').find({
318
+ where: {
319
+ domain: { id: domain.id },
320
+ ownerEmployee: { id: employee1.id }
321
+ }
322
+ })
323
+
324
+ // Then
325
+ expect(employee1Lines.length).toBe(1)
326
+ })
327
+ })
328
+ })
329
+ })
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Department Unit Tests
3
+ * 부서 엔티티 단위 테스트
4
+ */
5
+
6
+ import { TestDatabase } from '../../../../test/test-database'
7
+ import { withTestTransaction } from '../../../../test/test-context'
8
+ import {
9
+ domainFactory,
10
+ employeeFactory,
11
+ departmentFactory
12
+ } from '../../../../test/factories'
13
+
14
+ describe('Department Entity Tests', () => {
15
+ let testDb: TestDatabase
16
+
17
+ beforeAll(async () => {
18
+ testDb = TestDatabase.getInstance()
19
+ })
20
+
21
+ describe('Department 생성', () => {
22
+ it('Department가 올바르게 생성되어야 한다', async () => {
23
+ await withTestTransaction(async (context) => {
24
+ const { tx, domain } = context.state
25
+
26
+ // When
27
+ const department = await departmentFactory.createWithDomain(
28
+ {
29
+ controlNo: 'DEPT-001',
30
+ name: 'Engineering',
31
+ description: 'Engineering Department'
32
+ },
33
+ domain,
34
+ tx
35
+ )
36
+
37
+ // Then
38
+ expect(department).toBeDefined()
39
+ expect(department.id).toBeDefined()
40
+ expect(department.controlNo).toBe('DEPT-001')
41
+ expect(department.name).toBe('Engineering')
42
+ expect(department.description).toBe('Engineering Department')
43
+ expect(department.domain?.id).toBe(domain.id)
44
+ })
45
+ })
46
+
47
+ it('controlNo는 필수 필드여야 한다', async () => {
48
+ await withTestTransaction(async (context) => {
49
+ const { tx, domain } = context.state
50
+
51
+ // When
52
+ const department = await departmentFactory.createWithDomain(
53
+ { controlNo: 'REQUIRED-DEPT-001' },
54
+ domain,
55
+ tx
56
+ )
57
+
58
+ // Then
59
+ expect(department.controlNo).toBe('REQUIRED-DEPT-001')
60
+ })
61
+ })
62
+
63
+ it('active 상태가 관리되어야 한다', async () => {
64
+ await withTestTransaction(async (context) => {
65
+ const { tx, domain } = context.state
66
+
67
+ // When: active=true
68
+ const activeDept = await departmentFactory.createWithDomain(
69
+ { active: true },
70
+ domain,
71
+ tx
72
+ )
73
+
74
+ // When: active=false
75
+ const inactiveDept = await departmentFactory.createWithDomain(
76
+ { active: false },
77
+ domain,
78
+ tx
79
+ )
80
+
81
+ // Then
82
+ expect(activeDept.active).toBe(true)
83
+ expect(inactiveDept.active).toBe(false)
84
+ })
85
+ })
86
+ })
87
+
88
+ describe('Department 계층 구조', () => {
89
+ it('Parent-Child 관계가 설정되어야 한다', async () => {
90
+ await withTestTransaction(async (context) => {
91
+ const { tx, domain } = context.state
92
+
93
+ // Given
94
+ const parent = await departmentFactory.createWithDomain(
95
+ { name: 'Parent Department' },
96
+ domain,
97
+ tx
98
+ )
99
+
100
+ // When
101
+ const child = await departmentFactory.createChildDepartment(
102
+ { name: 'Child Department' },
103
+ parent,
104
+ tx
105
+ )
106
+
107
+ // Then
108
+ expect(child.parent?.id).toBe(parent.id)
109
+ })
110
+ })
111
+
112
+ it('다단계 계층 구조가 생성되어야 한다', async () => {
113
+ await withTestTransaction(async (context) => {
114
+ const { tx, domain } = context.state
115
+
116
+ // When
117
+ const { root, child, grandchild } = await departmentFactory.createHierarchy(domain, tx)
118
+
119
+ // Then
120
+ expect(root.parent).toBeUndefined()
121
+ expect(child.parent?.id).toBe(root.id)
122
+ expect(grandchild.parent?.id).toBe(child.id)
123
+ })
124
+ })
125
+
126
+ it('같은 parent 아래 여러 child가 있을 수 있어야 한다', async () => {
127
+ await withTestTransaction(async (context) => {
128
+ const { tx, domain } = context.state
129
+
130
+ // Given
131
+ const parent = await departmentFactory.createWithDomain(
132
+ { name: 'Parent' },
133
+ domain,
134
+ tx
135
+ )
136
+
137
+ // When
138
+ const child1 = await departmentFactory.createChildDepartment(
139
+ { name: 'Child 1' },
140
+ parent,
141
+ tx
142
+ )
143
+ const child2 = await departmentFactory.createChildDepartment(
144
+ { name: 'Child 2' },
145
+ parent,
146
+ tx
147
+ )
148
+ const child3 = await departmentFactory.createChildDepartment(
149
+ { name: 'Child 3' },
150
+ parent,
151
+ tx
152
+ )
153
+
154
+ // Then
155
+ expect(child1.parent?.id).toBe(parent.id)
156
+ expect(child2.parent?.id).toBe(parent.id)
157
+ expect(child3.parent?.id).toBe(parent.id)
158
+ })
159
+ })
160
+ })
161
+
162
+ describe('Department-Manager 관계', () => {
163
+ it('Department에 Manager를 설정할 수 있어야 한다', async () => {
164
+ await withTestTransaction(async (context) => {
165
+ const { tx, domain } = context.state
166
+
167
+ // Given
168
+ const department = await departmentFactory.createWithDomain({}, domain, tx)
169
+ const manager = await employeeFactory.createManager({ department }, domain, tx)
170
+
171
+ // When
172
+ department.manager = manager
173
+ const updated = await tx.save('Department', department)
174
+
175
+ // Then
176
+ expect(updated.manager?.id).toBe(manager.id)
177
+ })
178
+ })
179
+
180
+ it('Manager 없이도 Department가 존재할 수 있어야 한다', async () => {
181
+ await withTestTransaction(async (context) => {
182
+ const { tx, domain } = context.state
183
+
184
+ // When
185
+ const department = await departmentFactory.createWithDomain({}, domain, tx)
186
+
187
+ // Then
188
+ expect(department.manager).toBeUndefined()
189
+ })
190
+ })
191
+ })
192
+
193
+ describe('Department 조회', () => {
194
+ it('Domain으로 부서를 조회할 수 있어야 한다', async () => {
195
+ await withTestTransaction(async (context) => {
196
+ const { tx, domain } = context.state
197
+
198
+ // Given
199
+ await departmentFactory.createWithDomain({ name: 'Dept A' }, domain, tx)
200
+ await departmentFactory.createWithDomain({ name: 'Dept B' }, domain, tx)
201
+ await departmentFactory.createWithDomain({ name: 'Dept C' }, domain, tx)
202
+
203
+ // When
204
+ const departments = await tx.getRepository('Department').find({
205
+ where: { domain: { id: domain.id } }
206
+ })
207
+
208
+ // Then
209
+ expect(departments.length).toBe(3)
210
+ })
211
+ })
212
+
213
+ it('controlNo로 부서를 조회할 수 있어야 한다', async () => {
214
+ await withTestTransaction(async (context) => {
215
+ const { tx, domain } = context.state
216
+
217
+ // Given
218
+ await departmentFactory.createWithDomain(
219
+ { controlNo: 'UNIQUE-DEPT-001' },
220
+ domain,
221
+ tx
222
+ )
223
+
224
+ // When
225
+ const department = await tx.getRepository('Department').findOne({
226
+ where: { domain: { id: domain.id }, controlNo: 'UNIQUE-DEPT-001' }
227
+ })
228
+
229
+ // Then
230
+ expect(department).toBeDefined()
231
+ expect(department?.controlNo).toBe('UNIQUE-DEPT-001')
232
+ })
233
+ })
234
+ })
235
+
236
+ describe('Department 속성', () => {
237
+ it('extension(내선번호)이 저장되어야 한다', async () => {
238
+ await withTestTransaction(async (context) => {
239
+ const { tx, domain } = context.state
240
+
241
+ // When
242
+ const department = await departmentFactory.createWithDomain(
243
+ { extension: '1234' },
244
+ domain,
245
+ tx
246
+ )
247
+
248
+ // Then
249
+ expect(department.extension).toBe('1234')
250
+ })
251
+ })
252
+ })
253
+ })