@things-factory/organization 10.1.81 → 10.1.86

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 (45) hide show
  1. package/dist-client/component/department-selector.d.ts +1 -1
  2. package/dist-client/component/department-selector.js +13 -5
  3. package/dist-client/component/department-selector.js.map +1 -1
  4. package/dist-client/filters-form/ox-filter-department-object.js +1 -1
  5. package/dist-client/filters-form/ox-filter-department-object.js.map +1 -1
  6. package/dist-client/grist-editor/grist-editor-department-object.js +1 -1
  7. package/dist-client/grist-editor/grist-editor-department-object.js.map +1 -1
  8. package/dist-client/pages/department/department-list-page.js +13 -10
  9. package/dist-client/pages/department/department-list-page.js.map +1 -1
  10. package/dist-client/pages/department/department-patch.d.ts +9 -0
  11. package/dist-client/pages/department/department-patch.js +22 -0
  12. package/dist-client/pages/department/department-patch.js.map +1 -0
  13. package/dist-client/tsconfig.tsbuildinfo +1 -1
  14. package/dist-server/service/department/department-mutation.js +14 -3
  15. package/dist-server/service/department/department-mutation.js.map +1 -1
  16. package/dist-server/service/department/department-parent.d.ts +10 -0
  17. package/dist-server/service/department/department-parent.js +35 -0
  18. package/dist-server/service/department/department-parent.js.map +1 -0
  19. package/dist-server/service/employee/employee-day.d.ts +5 -0
  20. package/dist-server/service/employee/employee-day.js +33 -0
  21. package/dist-server/service/employee/employee-day.js.map +1 -0
  22. package/dist-server/service/employee/employee-history.d.ts +2 -2
  23. package/dist-server/service/employee/employee-history.js +4 -4
  24. package/dist-server/service/employee/employee-history.js.map +1 -1
  25. package/dist-server/service/employee/employee-mutation.js +6 -5
  26. package/dist-server/service/employee/employee-mutation.js.map +1 -1
  27. package/dist-server/service/employee/employee-type.d.ts +5 -5
  28. package/dist-server/service/employee/employee-type.js +10 -10
  29. package/dist-server/service/employee/employee-type.js.map +1 -1
  30. package/dist-server/service/employee/employee.d.ts +2 -2
  31. package/dist-server/service/employee/employee.js +4 -4
  32. package/dist-server/service/employee/employee.js.map +1 -1
  33. package/dist-server/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +2 -2
  35. package/spec/unit/employee.spec.ts +2 -2
  36. package/tests/department-parent.test.ts +185 -0
  37. package/tests/department-patch.test.ts +52 -0
  38. package/tests/department-selector.test.ts +32 -0
  39. package/tests/employee-day.test.ts +163 -0
  40. package/translations/en.json +2 -0
  41. package/translations/ja.json +2 -0
  42. package/translations/ko.json +2 -0
  43. package/translations/ms.json +2 -0
  44. package/translations/zh.json +2 -0
  45. package/dist-client/tsconfig.chk.tsbuildinfo +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/organization",
3
- "version": "10.1.81",
3
+ "version": "10.1.86",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -41,5 +41,5 @@
41
41
  "@things-factory/contact": "^10.1.79",
42
42
  "@things-factory/shell": "^10.1.79"
43
43
  },
44
- "gitHead": "0c4f91d92c8214a9b2eca6690a7ed48bd2d828cc"
44
+ "gitHead": "8e56ed31b1c81934257f2938b9b1a51f1852773f"
45
45
  }
@@ -188,8 +188,8 @@ describe('Employee Entity Tests', () => {
188
188
  const { tx, domain } = context.state
189
189
 
190
190
  // Given
191
- const hiredOn = new Date('2020-01-15')
192
- const retiredOn = new Date('2024-12-31')
191
+ const hiredOn = '2020-01-15'
192
+ const retiredOn = '2024-12-31'
193
193
 
194
194
  // When
195
195
  const employee = await employeeFactory.createWithDomain(
@@ -0,0 +1,185 @@
1
+ /**
2
+ * A department's parent can be set, changed and cleared from the department list, and a loop is refused.
3
+ *
4
+ * The list had no parent cell. Departments made anywhere but "add child node" on a new row stayed top-level
5
+ * for good — OA's five all were — and the department selector, which drew the first top-level department
6
+ * only, could offer one of the five.
7
+ *
8
+ * This sends the list's patch through the server's own DepartmentPatch and the real updateMultipleDepartment,
9
+ * on sqlite, and reads the parent back from the table.
10
+ */
11
+ import 'reflect-metadata'
12
+
13
+ const { existsSync } = require('fs')
14
+ const { join } = require('path')
15
+ const { DataSource } = require('typeorm')
16
+ const { buildSchema, Resolver, Query, Mutation, Arg, Ctx } = require('type-graphql')
17
+ const { graphql } = require('graphql')
18
+
19
+ const shell = require('@things-factory/shell')
20
+ const authBase = require('@things-factory/auth-base')
21
+ const contact = require('@things-factory/contact')
22
+
23
+ function loadCompiled(modulePath: string): any {
24
+ const resolved = join(__dirname, '..', 'dist-server', `${modulePath}.js`)
25
+ if (!existsSync(resolved)) {
26
+ throw new Error(`organization build output is missing: ${resolved}\nBuild it first: npm run build:server`)
27
+ }
28
+ return require(resolved)
29
+ }
30
+
31
+ let dataSource: any
32
+ let Department: any
33
+ let schema: any
34
+ let domain: any
35
+ let user: any
36
+
37
+ const SAVE = `
38
+ mutation ($patches: [DepartmentPatch!]!) {
39
+ updateMultipleDepartment(patches: $patches) { id name }
40
+ }
41
+ `
42
+
43
+ beforeAll(async () => {
44
+ const organization = loadCompiled('service/index')
45
+ Department = loadCompiled('service/department/department').Department
46
+ const { DepartmentMutation } = loadCompiled('service/department/department-mutation')
47
+ const { DepartmentPatch } = loadCompiled('service/department/department-type')
48
+
49
+ dataSource = new DataSource({
50
+ type: 'sqlite',
51
+ database: ':memory:',
52
+ synchronize: true,
53
+ dropSchema: true,
54
+ namingStrategy: new shell.NamingStrategy(),
55
+ entities: [...shell.entities, ...authBase.entities, ...contact.entities, ...organization.entities],
56
+ logging: false
57
+ })
58
+ await dataSource.initialize()
59
+ shell.addDataSource('default', dataSource)
60
+ shell.addDataSource('tx', dataSource)
61
+
62
+ /* The real mutation behind the real input type; the context stands in for what its directives set up. */
63
+ class DepartmentParentProbe {
64
+ probe() {
65
+ return true
66
+ }
67
+
68
+ async updateMultipleDepartment(patches: any[], context: any) {
69
+ return await new DepartmentMutation().updateMultipleDepartment(patches, context)
70
+ }
71
+ }
72
+ Query(() => Boolean)(DepartmentParentProbe.prototype, 'probe', Object.getOwnPropertyDescriptor(DepartmentParentProbe.prototype, 'probe'))
73
+ Arg('patches', () => [DepartmentPatch])(DepartmentParentProbe.prototype, 'updateMultipleDepartment', 0)
74
+ Ctx()(DepartmentParentProbe.prototype, 'updateMultipleDepartment', 1)
75
+ Mutation(() => [Department])(
76
+ DepartmentParentProbe.prototype,
77
+ 'updateMultipleDepartment',
78
+ Object.getOwnPropertyDescriptor(DepartmentParentProbe.prototype, 'updateMultipleDepartment')
79
+ )
80
+ Resolver()(DepartmentParentProbe)
81
+
82
+ schema = await buildSchema({ resolvers: [DepartmentParentProbe], validate: false })
83
+ })
84
+
85
+ let ceo: any
86
+ let team1: any
87
+ let team2: any
88
+
89
+ beforeEach(async () => {
90
+ await dataSource.synchronize(true)
91
+ domain = await dataSource.getRepository(shell.Domain).save({ name: 'department-parent', subdomain: 'department-parent' })
92
+ user = await dataSource.getRepository(authBase.User).save({ name: 'tester', email: 'tester@department-parent.test' })
93
+
94
+ /* OA's shape: every department top-level. */
95
+ const repository = dataSource.getRepository(Department)
96
+ ceo = await repository.save({ domain, controlNo: 'CEO', name: 'CEO' })
97
+ team1 = await repository.save({ domain, controlNo: 'T01', name: '1팀' })
98
+ team2 = await repository.save({ domain, controlNo: 'T02', name: '2팀' })
99
+ })
100
+
101
+ afterAll(async () => {
102
+ if (!dataSource) return
103
+ shell.removeDataSource('default')
104
+ shell.removeDataSource('tx')
105
+ await dataSource.destroy()
106
+ })
107
+
108
+ async function save(patches: any[]) {
109
+ return await graphql({
110
+ schema,
111
+ source: SAVE,
112
+ variableValues: { patches },
113
+ contextValue: { state: { domain, user, tx: dataSource.manager } }
114
+ })
115
+ }
116
+
117
+ async function parentOf(department: any) {
118
+ return (await dataSource.getRepository(Department).findOneBy({ id: department.id })).parentId ?? null
119
+ }
120
+
121
+ describe("a department's parent is set from the department list", () => {
122
+ it('an existing top-level department moves under another', async () => {
123
+ const result = await save([
124
+ { id: team1.id, cuFlag: 'M', parent: { id: ceo.id } },
125
+ { id: team2.id, cuFlag: 'M', parent: { id: ceo.id } }
126
+ ])
127
+
128
+ expect(result.errors).toBeUndefined()
129
+ expect(await parentOf(team1)).toBe(ceo.id)
130
+ expect(await parentOf(team2)).toBe(ceo.id)
131
+ expect(await parentOf(ceo)).toBeNull()
132
+ })
133
+
134
+ it('moves from one parent to another, and back to top-level with null', async () => {
135
+ await save([{ id: team2.id, cuFlag: 'M', parent: { id: ceo.id } }])
136
+
137
+ expect((await save([{ id: team2.id, cuFlag: 'M', parent: { id: team1.id } }])).errors).toBeUndefined()
138
+ expect(await parentOf(team2)).toBe(team1.id)
139
+
140
+ expect((await save([{ id: team2.id, cuFlag: 'M', parent: null }])).errors).toBeUndefined()
141
+ expect(await parentOf(team2)).toBeNull()
142
+ })
143
+
144
+ it('an edit that does not name the parent leaves it where it is', async () => {
145
+ await save([{ id: team1.id, cuFlag: 'M', parent: { id: ceo.id } }])
146
+
147
+ expect((await save([{ id: team1.id, cuFlag: 'M', description: 'first team' }])).errors).toBeUndefined()
148
+ expect(await parentOf(team1)).toBe(ceo.id)
149
+ })
150
+
151
+ it('a new row added under a department is saved there', async () => {
152
+ const result = await save([{ cuFlag: '+', controlNo: 'T01-A', name: '1팀 A조', parent: { id: team1.id } }])
153
+
154
+ expect(result.errors).toBeUndefined()
155
+ const created = await dataSource.getRepository(Department).findOneBy({ controlNo: 'T01-A' })
156
+ expect(created.parentId).toBe(team1.id)
157
+ })
158
+
159
+ it('a department cannot be its own parent', async () => {
160
+ const result = await save([{ id: team1.id, cuFlag: 'M', parent: { id: team1.id } }])
161
+
162
+ expect(result.errors?.[0]?.message).toMatch(/cannot be placed under itself/)
163
+ expect(await parentOf(team1)).toBeNull()
164
+ })
165
+
166
+ it('a department cannot go under one of its own sub-departments', async () => {
167
+ await save([{ id: team1.id, cuFlag: 'M', parent: { id: ceo.id } }])
168
+ await save([{ id: team2.id, cuFlag: 'M', parent: { id: team1.id } }])
169
+
170
+ const result = await save([{ id: ceo.id, cuFlag: 'M', parent: { id: team2.id } }])
171
+
172
+ expect(result.errors?.[0]?.message).toMatch(/cannot be placed under itself/)
173
+ expect(await parentOf(ceo)).toBeNull()
174
+ })
175
+
176
+ it('a parent from another domain is not found', async () => {
177
+ const other = await dataSource.getRepository(shell.Domain).save({ name: 'other', subdomain: 'other' })
178
+ const stranger = await dataSource.getRepository(Department).save({ domain: other, controlNo: 'X', name: 'X' })
179
+
180
+ const result = await save([{ id: team1.id, cuFlag: 'M', parent: { id: stranger.id } }])
181
+
182
+ expect(result.errors?.[0]?.message).toMatch(/parent department not found/)
183
+ expect(await parentOf(team1)).toBeNull()
184
+ })
185
+ })
@@ -0,0 +1,52 @@
1
+ import { toDepartmentPatch } from '../client/pages/department/department-patch'
2
+
3
+ /**
4
+ * The department list's save sends the parent as a reference.
5
+ *
6
+ * The parent cell's selector hands back { id, controlNo, name, description }. DepartmentPatch.parent is an
7
+ * ObjectRef, which has no controlNo, so the whole patch would be refused if the cell's value went as it is.
8
+ */
9
+ describe('the department list sends a parent as { id } or null', () => {
10
+ const picked = { id: 'd-ceo', controlNo: 'CEO', name: 'CEO', description: 'office' }
11
+
12
+ it('a changed parent cell goes as its id only', () => {
13
+ const patch = toDepartmentPatch({
14
+ id: 'd-t01',
15
+ __dirty__: 'M',
16
+ __dirtyfields__: { parent: { before: null, after: picked } }
17
+ })
18
+
19
+ expect(patch).toEqual({ id: 'd-t01', parent: { id: 'd-ceo' }, cuFlag: 'M' })
20
+ })
21
+
22
+ it('a cleared parent cell goes as null, which makes the department top-level', () => {
23
+ const patch = toDepartmentPatch({
24
+ id: 'd-t01',
25
+ __dirty__: 'M',
26
+ __dirtyfields__: { parent: { before: picked, after: null } }
27
+ })
28
+
29
+ expect(patch.parent).toBeNull()
30
+ })
31
+
32
+ it('an edit of another cell does not send the parent the row was drawn with', () => {
33
+ const patch = toDepartmentPatch({
34
+ id: 'd-t01',
35
+ parent: { id: 'd-ceo', name: 'CEO' },
36
+ __dirty__: 'M',
37
+ __dirtyfields__: { description: { before: '', after: 'first team' } }
38
+ })
39
+
40
+ expect(patch).toEqual({ id: 'd-t01', description: 'first team', cuFlag: 'M' })
41
+ })
42
+
43
+ it('a row added with "add child node" sends the parent it was added under', () => {
44
+ const patch = toDepartmentPatch({
45
+ parent: { id: 'd-t01' },
46
+ __dirty__: '+',
47
+ __dirtyfields__: { name: { before: undefined, after: '1팀 A조' } }
48
+ })
49
+
50
+ expect(patch).toEqual({ name: '1팀 A조', parent: { id: 'd-t01' }, cuFlag: '+' })
51
+ })
52
+ })
@@ -0,0 +1,32 @@
1
+ import { readFileSync } from 'fs'
2
+ import path from 'path'
3
+
4
+ /**
5
+ * The department picker's boxes are headed by the department code, and its popup says what is picked.
6
+ *
7
+ * ox-tree-vertical prints `id-property` as the header of every box. Set to the database id, the picker drew
8
+ * a UUID over each department (09-30, OA). Opened from a grid cell or a filter, the popup was titled
9
+ * "select item".
10
+ */
11
+ const read = (file: string) => readFileSync(path.resolve(__dirname, '..', file), 'utf-8')
12
+
13
+ describe('the department picker', () => {
14
+ it('heads each box with the department code', () => {
15
+ const source = read('client/component/department-selector.ts')
16
+ const tag = source.slice(source.indexOf('<ox-tree-vertical'), source.indexOf('</ox-tree-vertical>'))
17
+ expect(tag).toMatch(/\sid-property="controlNo"/)
18
+ })
19
+
20
+ it.each(['client/grist-editor/grist-editor-department-object.ts', 'client/filters-form/ox-filter-department-object.ts'])(
21
+ '%s titles the popup as picking a department',
22
+ file => {
23
+ const source = read(file)
24
+ expect(source.includes("i18next.t('title.select department')")).toBe(true)
25
+ expect(source.includes('title.select_item')).toBe(false)
26
+ }
27
+ )
28
+
29
+ it.each(['en', 'ko', 'ja', 'zh', 'ms'])('the title is translated in %s', lang => {
30
+ expect(JSON.parse(read(`translations/${lang}.json`))['title.select department']).toBeTruthy()
31
+ })
32
+ })
@@ -0,0 +1,163 @@
1
+ /**
2
+ * hiredOn and retiredOn are calendar days, and the grid's date editor sends them as 'YYYY-MM-DD'.
3
+ *
4
+ * They were declared `Date`, so GraphQL typed them as a date-time and refused the grid's own value before
5
+ * any resolver ran: 'Variable "$patches" got invalid value "2025-12-31" at "patches[0].retiredOn"'. The
6
+ * employee list could not save a retire day at all.
7
+ *
8
+ * This sends the grid's patch through the server's own EmployeePatch and Employee types and the real
9
+ * updateMultipleEmployee, on sqlite, and reads the day back — from the table, from the history row and
10
+ * through GraphQL — unchanged.
11
+ */
12
+ import 'reflect-metadata'
13
+
14
+ const { existsSync } = require('fs')
15
+ const { join } = require('path')
16
+ const { DataSource } = require('typeorm')
17
+ const { buildSchema, Resolver, Query, Mutation, Arg, Ctx } = require('type-graphql')
18
+ const { graphql } = require('graphql')
19
+
20
+ const shell = require('@things-factory/shell')
21
+ const authBase = require('@things-factory/auth-base')
22
+ const contact = require('@things-factory/contact')
23
+
24
+ function loadCompiled(modulePath: string): any {
25
+ const resolved = join(__dirname, '..', 'dist-server', `${modulePath}.js`)
26
+ if (!existsSync(resolved)) {
27
+ throw new Error(`organization build output is missing: ${resolved}\nBuild it first: npm run build:server`)
28
+ }
29
+ return require(resolved)
30
+ }
31
+
32
+ let dataSource: any
33
+ let organization: any
34
+ let Employee: any
35
+ let EmployeeHistory: any
36
+ let schema: any
37
+ let domain: any
38
+ let user: any
39
+
40
+ const SAVE = `
41
+ mutation ($patches: [EmployeePatch!]!) {
42
+ updateMultipleEmployee(patches: $patches) { id hiredOn retiredOn }
43
+ }
44
+ `
45
+
46
+ beforeAll(async () => {
47
+ organization = loadCompiled('service/index')
48
+ Employee = loadCompiled('service/employee/employee').Employee
49
+ EmployeeHistory = loadCompiled('service/employee/employee-history').EmployeeHistory
50
+ const { EmployeeHistoryEntitySubscriber } = loadCompiled('service/employee/event-subscriber')
51
+ const { EmployeeMutation } = loadCompiled('service/employee/employee-mutation')
52
+ const { EmployeePatch } = loadCompiled('service/employee/employee-type')
53
+
54
+ dataSource = new DataSource({
55
+ type: 'sqlite',
56
+ database: ':memory:',
57
+ synchronize: true,
58
+ dropSchema: true,
59
+ namingStrategy: new shell.NamingStrategy(),
60
+ entities: [...shell.entities, ...authBase.entities, ...contact.entities, ...organization.entities],
61
+ subscribers: [EmployeeHistoryEntitySubscriber],
62
+ logging: false
63
+ })
64
+ await dataSource.initialize()
65
+ shell.addDataSource('default', dataSource)
66
+ shell.addDataSource('tx', dataSource)
67
+
68
+ /* The real mutation, behind the real input and output types. Its @transaction and @privilege directives are
69
+ * the framework's; the context below stands in for what they set up. */
70
+ class EmployeeDayProbe {
71
+ /* A schema needs a Query field; this one is never asked. */
72
+ probe() {
73
+ return true
74
+ }
75
+
76
+ async updateMultipleEmployee(patches: any[], context: any) {
77
+ return await new EmployeeMutation().updateMultipleEmployee(patches, context)
78
+ }
79
+ }
80
+ Arg('patches', () => [EmployeePatch])(EmployeeDayProbe.prototype, 'updateMultipleEmployee', 0)
81
+ Ctx()(EmployeeDayProbe.prototype, 'updateMultipleEmployee', 1)
82
+ Mutation(() => [Employee])(
83
+ EmployeeDayProbe.prototype,
84
+ 'updateMultipleEmployee',
85
+ Object.getOwnPropertyDescriptor(EmployeeDayProbe.prototype, 'updateMultipleEmployee')
86
+ )
87
+ Query(() => Boolean)(EmployeeDayProbe.prototype, 'probe', Object.getOwnPropertyDescriptor(EmployeeDayProbe.prototype, 'probe'))
88
+ Resolver()(EmployeeDayProbe)
89
+
90
+ schema = await buildSchema({ resolvers: [EmployeeDayProbe], validate: false })
91
+ })
92
+
93
+ beforeEach(async () => {
94
+ await dataSource.synchronize(true)
95
+ domain = await dataSource.getRepository(shell.Domain).save({ name: 'employee-day', subdomain: 'employee-day' })
96
+ user = await dataSource.getRepository(authBase.User).save({ name: 'tester', email: 'tester@employee-day.test' })
97
+ })
98
+
99
+ afterAll(async () => {
100
+ if (!dataSource) return
101
+ shell.removeDataSource('default')
102
+ shell.removeDataSource('tx')
103
+ await dataSource.destroy()
104
+ })
105
+
106
+ async function save(patches: any[]) {
107
+ return await graphql({
108
+ schema,
109
+ source: SAVE,
110
+ variableValues: { patches },
111
+ contextValue: { state: { domain, user, tx: dataSource.manager } }
112
+ })
113
+ }
114
+
115
+ async function seedEmployee() {
116
+ return await dataSource.getRepository(Employee).save({ domain, controlNo: 'E-1', name: 'Kim', hiredOn: '2020-01-15' })
117
+ }
118
+
119
+ describe('employee hire and retire days are calendar days', () => {
120
+ it("the grid's patch with retiredOn '2025-12-31' saves and reads back unchanged", async () => {
121
+ const employee = await seedEmployee()
122
+
123
+ const result = await save([{ id: employee.id, cuFlag: 'M', retiredOn: '2025-12-31' }])
124
+
125
+ expect(result.errors).toBeUndefined()
126
+ expect(result.data.updateMultipleEmployee).toEqual([{ id: employee.id, hiredOn: '2020-01-15', retiredOn: '2025-12-31' }])
127
+
128
+ const stored = await dataSource.getRepository(Employee).findOneBy({ id: employee.id })
129
+ expect(stored.retiredOn).toBe('2025-12-31')
130
+ expect(stored.hiredOn).toBe('2020-01-15')
131
+
132
+ const history = await dataSource.getRepository(EmployeeHistory).find({ where: { originalId: employee.id } })
133
+ expect(history.map(row => row.retiredOn)).toContain('2025-12-31')
134
+ })
135
+
136
+ it('hiredOn edited in the grid saves too', async () => {
137
+ const employee = await seedEmployee()
138
+
139
+ const result = await save([{ id: employee.id, cuFlag: 'M', hiredOn: '2021-03-02' }])
140
+
141
+ expect(result.errors).toBeUndefined()
142
+ expect((await dataSource.getRepository(Employee).findOneBy({ id: employee.id })).hiredOn).toBe('2021-03-02')
143
+ })
144
+
145
+ it("a cleared date cell ('') removes the day", async () => {
146
+ const employee = await seedEmployee()
147
+
148
+ const result = await save([{ id: employee.id, cuFlag: 'M', hiredOn: '' }])
149
+
150
+ expect(result.errors).toBeUndefined()
151
+ expect((await dataSource.getRepository(Employee).findOneBy({ id: employee.id })).hiredOn).toBeNull()
152
+ })
153
+
154
+ it('a value that is not a calendar day is refused, not stored', async () => {
155
+ const employee = await seedEmployee()
156
+
157
+ for (const value of ['2025-12-31T00:00:00.000Z', '2025-02-30', '45657']) {
158
+ const result = await save([{ id: employee.id, cuFlag: 'M', retiredOn: value }])
159
+ expect(result.errors?.[0]?.message).toMatch(/retiredOn must be a calendar day/)
160
+ }
161
+ expect((await dataSource.getRepository(Employee).findOneBy({ id: employee.id })).retiredOn).toBeNull()
162
+ })
163
+ })
@@ -19,6 +19,7 @@
19
19
  "field.notify-after": "notify at",
20
20
  "field.owner": "owner",
21
21
  "field.owner-type": "owner type",
22
+ "field.parent-department": "parent department",
22
23
  "field.participation": "capacity",
23
24
  "field.register-account": "account",
24
25
  "field.supervisor": "supervisor",
@@ -80,6 +81,7 @@
80
81
  "title.import employee": "import employees",
81
82
  "title.lookup system-user": "lookup system user",
82
83
  "title.my-approval-line template list": "my approval lines",
84
+ "title.select department": "select department",
83
85
  "title.organization": "organization",
84
86
  "title.recipients editor": "recipients editor",
85
87
  "title.person-membership": "membership",
@@ -19,6 +19,7 @@
19
19
  "field.notify-after": "通知時点",
20
20
  "field.owner": "所有者",
21
21
  "field.owner-type": "所有者タイプ",
22
+ "field.parent-department": "上位部署",
22
23
  "field.participation": "資格",
23
24
  "field.register-account": "アカウント登録",
24
25
  "field.supervisor": "監督者",
@@ -80,6 +81,7 @@
80
81
  "title.import employee": "社員の取り込み",
81
82
  "title.lookup system-user": "システムユーザーを検索",
82
83
  "title.my-approval-line template list": "自分の決裁ライン",
84
+ "title.select department": "部署選択",
83
85
  "title.organization": "組織",
84
86
  "title.recipients editor": "受信者リストの編集",
85
87
  "title.person-membership": "所属",
@@ -19,6 +19,7 @@
19
19
  "field.notify-after": "알림 시점",
20
20
  "field.owner": "소유자",
21
21
  "field.owner-type": "소유자 종류",
22
+ "field.parent-department": "상위 부서",
22
23
  "field.participation": "자격",
23
24
  "field.register-account": "계정 등록",
24
25
  "field.supervisor": "감독자",
@@ -80,6 +81,7 @@
80
81
  "title.import employee": "직원 가져오기",
81
82
  "title.lookup system-user": "시스템 사용자 검색",
82
83
  "title.my-approval-line template list": "내 결재선",
84
+ "title.select department": "부서 선택",
83
85
  "title.organization": "조직",
84
86
  "title.recipients editor": "수신자 목록 편집",
85
87
  "title.person-membership": "소속",
@@ -19,6 +19,7 @@
19
19
  "field.notify-after": "masa notis",
20
20
  "field.owner": "pemilik",
21
21
  "field.owner-type": "jenis pemilik",
22
+ "field.parent-department": "jabatan induk",
22
23
  "field.participation": "kapasiti",
23
24
  "field.register-account": "daftar akaun",
24
25
  "field.supervisor": "penyelia",
@@ -80,6 +81,7 @@
80
81
  "title.import employee": "import pekerja",
81
82
  "title.lookup system-user": "cari pengguna sistem",
82
83
  "title.my-approval-line template list": "talian kelulusan saya",
84
+ "title.select department": "pilih jabatan",
83
85
  "title.organization": "organisasi",
84
86
  "title.recipients editor": "penyunting penerima",
85
87
  "title.person-membership": "keanggotaan",
@@ -19,6 +19,7 @@
19
19
  "field.notify-after": "通知时点",
20
20
  "field.owner": "所有者",
21
21
  "field.owner-type": "所有者类型",
22
+ "field.parent-department": "上级部门",
22
23
  "field.participation": "身份",
23
24
  "field.register-account": "注册账户",
24
25
  "field.supervisor": "主管",
@@ -80,6 +81,7 @@
80
81
  "title.import employee": "导入员工",
81
82
  "title.lookup system-user": "查找系统用户",
82
83
  "title.my-approval-line template list": "我的审批线",
84
+ "title.select department": "选择部门",
83
85
  "title.organization": "组织",
84
86
  "title.recipients editor": "收件人列表编辑",
85
87
  "title.person-membership": "所属",