@things-factory/organization 8.0.0-beta.8 → 8.0.0

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 (94) hide show
  1. package/client/bootstrap.ts +23 -0
  2. package/client/component/approval-line-brief.ts +119 -0
  3. package/client/component/approval-line-items-editor-popup.ts +91 -0
  4. package/client/component/approval-line-items-editor.ts +325 -0
  5. package/client/component/approval-line-selector.ts +235 -0
  6. package/client/component/approval-line-templates-manager.ts +229 -0
  7. package/client/component/approval-line-view.ts +122 -0
  8. package/client/component/assignees-editor-popup.ts +79 -0
  9. package/client/component/assignees-editor.ts +217 -0
  10. package/client/component/assignees-view.ts +55 -0
  11. package/client/component/department-selector.ts +151 -0
  12. package/client/component/department-view.ts +107 -0
  13. package/client/component/index.ts +16 -0
  14. package/client/component/recipients-editor-popup.ts +79 -0
  15. package/client/component/recipients-editor.ts +212 -0
  16. package/client/component/recipients-view.ts +55 -0
  17. package/client/grist-editor/grist-editor-approval-line.ts +70 -0
  18. package/client/grist-editor/grist-editor-assignees.ts +69 -0
  19. package/client/grist-editor/grist-editor-department-object.ts +78 -0
  20. package/client/grist-editor/grist-editor-recipients.ts +69 -0
  21. package/client/grist-editor/grist-renderer-approval-line.ts +13 -0
  22. package/client/grist-editor/grist-renderer-assignees.ts +13 -0
  23. package/client/grist-editor/grist-renderer-department-object.ts +13 -0
  24. package/client/grist-editor/grist-renderer-recipients.ts +13 -0
  25. package/client/index.ts +2 -0
  26. package/client/pages/approval-line/common-approval-line-templates-page.ts +382 -0
  27. package/client/pages/approval-line/my-approval-line-templates-page.ts +385 -0
  28. package/client/pages/department/department-importer.ts +87 -0
  29. package/client/pages/department/department-list-page.ts +450 -0
  30. package/client/pages/department/department-tree-page.ts +379 -0
  31. package/client/pages/employee/employee-importer.ts +87 -0
  32. package/client/pages/employee/employee-list-page.ts +772 -0
  33. package/client/pages/employee/employees-by-department.ts +519 -0
  34. package/client/route.ts +27 -0
  35. package/client/tsconfig.json +13 -0
  36. package/client/types/approval-line.ts +52 -0
  37. package/client/types/contact.ts +51 -0
  38. package/client/types/department.ts +29 -0
  39. package/client/types/employee.ts +50 -0
  40. package/client/types/index.ts +5 -0
  41. package/client/types/org-member.ts +27 -0
  42. package/dist-client/bootstrap.js +1 -8
  43. package/dist-client/bootstrap.js.map +1 -1
  44. package/dist-client/pages/employee/employee-list-page.js +3 -3
  45. package/dist-client/pages/employee/employee-list-page.js.map +1 -1
  46. package/dist-client/pages/employee/employees-by-department.js +2 -2
  47. package/dist-client/pages/employee/employees-by-department.js.map +1 -1
  48. package/dist-client/tsconfig.tsbuildinfo +1 -1
  49. package/dist-server/service/employee/employee-history.d.ts +2 -6
  50. package/dist-server/service/employee/employee-history.js +3 -23
  51. package/dist-server/service/employee/employee-history.js.map +1 -1
  52. package/dist-server/service/employee/employee-query.js +1 -1
  53. package/dist-server/service/employee/employee-query.js.map +1 -1
  54. package/dist-server/service/employee/employee-type.d.ts +5 -13
  55. package/dist-server/service/employee/employee-type.js +7 -39
  56. package/dist-server/service/employee/employee-type.js.map +1 -1
  57. package/dist-server/service/employee/employee.d.ts +2 -6
  58. package/dist-server/service/employee/employee.js +3 -23
  59. package/dist-server/service/employee/employee.js.map +1 -1
  60. package/dist-server/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +12 -12
  62. package/server/controllers/register-employee-as-system-user.ts +136 -0
  63. package/server/index.ts +3 -0
  64. package/server/migrations/1723861013111-seed-organization-codes.ts +127 -0
  65. package/server/migrations/index.ts +9 -0
  66. package/server/routes.ts +26 -0
  67. package/server/service/approval-line/approval-line-item.ts +42 -0
  68. package/server/service/approval-line/approval-line-mutation.ts +394 -0
  69. package/server/service/approval-line/approval-line-query.ts +208 -0
  70. package/server/service/approval-line/approval-line-type.ts +63 -0
  71. package/server/service/approval-line/approval-line.ts +123 -0
  72. package/server/service/approval-line/index.ts +7 -0
  73. package/server/service/department/department-history.ts +141 -0
  74. package/server/service/department/department-mutation.ts +231 -0
  75. package/server/service/department/department-query.ts +131 -0
  76. package/server/service/department/department-type.ts +74 -0
  77. package/server/service/department/department.ts +116 -0
  78. package/server/service/department/event-subscriber.ts +17 -0
  79. package/server/service/department/index.ts +9 -0
  80. package/server/service/employee/employee-history.ts +173 -0
  81. package/server/service/employee/employee-mutation.ts +386 -0
  82. package/server/service/employee/employee-query.ts +172 -0
  83. package/server/service/employee/employee-type.ts +176 -0
  84. package/server/service/employee/employee.ts +177 -0
  85. package/server/service/employee/event-subscriber.ts +17 -0
  86. package/server/service/employee/index.ts +9 -0
  87. package/server/service/index.ts +39 -0
  88. package/server/tsconfig.json +10 -0
  89. package/dist-client/filters-form/filter-department-object.d.ts +0 -3
  90. package/dist-client/filters-form/filter-department-object.js +0 -8
  91. package/dist-client/filters-form/filter-department-object.js.map +0 -1
  92. package/dist-client/filters-form/ox-filter-department-object.d.ts +0 -15
  93. package/dist-client/filters-form/ox-filter-department-object.js +0 -130
  94. package/dist-client/filters-form/ox-filter-department-object.js.map +0 -1
@@ -0,0 +1,379 @@
1
+ import '@operato/data-tree'
2
+ import '@operato/context/ox-context-page-toolbar.js'
3
+
4
+ import { css, html, PropertyValues } from 'lit'
5
+ import { customElement, property, query, state } from 'lit/decorators.js'
6
+ import { ScopedElementsMixin } from '@open-wc/scoped-elements'
7
+ import { client } from '@operato/graphql'
8
+ import { i18next, localize } from '@operato/i18n'
9
+ import { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'
10
+ import { CustomAlert, PageView, store } from '@operato/shell'
11
+
12
+ import { connect } from 'pwa-helpers/connect-mixin'
13
+ import gql from 'graphql-tag'
14
+
15
+ import { DepartmentImporter } from './department-importer'
16
+ import { Department } from '../../types/department'
17
+
18
+ import { DepartmentView } from '../../component/department-view'
19
+
20
+ const SubDepartmentFragment = gql`
21
+ fragment SubDepartmentFragment on Department {
22
+ id
23
+ controlNo
24
+ name
25
+ description
26
+
27
+ manager {
28
+ id
29
+ name
30
+ controlNo
31
+ photo
32
+ email
33
+ }
34
+ active
35
+ picture
36
+
37
+ updater {
38
+ id
39
+ name
40
+ }
41
+ updatedAt
42
+ }
43
+ `
44
+
45
+ @customElement('department-tree-page')
46
+ export class DepartmentTreePage extends connect(store)(localize(i18next)(ScopedElementsMixin(PageView))) {
47
+ static styles = [
48
+ CommonHeaderStyles,
49
+ ScrollbarStyles,
50
+ css`
51
+ :host {
52
+ display: flex;
53
+ flex-direction: column;
54
+
55
+ width: 100%;
56
+ overflow: auto;
57
+ }
58
+
59
+ content {
60
+ flex: 1;
61
+ display: flex;
62
+ flex-direction: row;
63
+ }
64
+
65
+ div[editor] {
66
+ width: 300px;
67
+ display: flex;
68
+ flex-direction: column;
69
+ }
70
+
71
+ ox-tree-vertical {
72
+ flex: 1;
73
+ overflow: auto;
74
+ }
75
+
76
+ department-view {
77
+ flex: 1;
78
+ padding: var(--spacing-medium);
79
+ background-color: var(--md-sys-color-surface-variant);
80
+ overflow: auto;
81
+ }
82
+
83
+ .footer button[disabled] {
84
+ color: var(--md-sys-color-surface-dim);
85
+ background-color: transparent;
86
+ }
87
+ `
88
+ ]
89
+
90
+ static get scopedElements() {
91
+ return {
92
+ 'department-importer': DepartmentImporter
93
+ }
94
+ }
95
+
96
+ @state() root?: Department
97
+ @state() selected?: Department
98
+ @state() department?: Department
99
+ @state() modified: boolean = false
100
+ @state() appendable: boolean = false
101
+
102
+ @query('department-view') departmentView!: DepartmentView
103
+
104
+ get context() {
105
+ return {
106
+ title: i18next.t('title.department list'),
107
+ help: 'organization/department',
108
+ actions: [
109
+ {
110
+ icon: 'delete',
111
+ title: i18next.t('button.delete'),
112
+ action: this.delete.bind(this),
113
+ emphasis: {
114
+ danger: true
115
+ }
116
+ }
117
+ ].filter(Boolean),
118
+ exportable: {
119
+ name: i18next.t('title.department list'),
120
+ data: this.exportHandler.bind(this)
121
+ },
122
+ importable: {
123
+ handler: this.importHandler.bind(this)
124
+ },
125
+ toolbar: false
126
+ }
127
+ }
128
+
129
+ render() {
130
+ return html`
131
+ <div class="header">
132
+ <ox-context-page-toolbar class="actions" .context=${this.context}></ox-context-page-toolbar>
133
+ </div>
134
+
135
+ <content>
136
+ <ox-tree-vertical
137
+ .data=${this.root}
138
+ .selected=${this.selected}
139
+ @select=${this.onSelect.bind(this)}
140
+ id-property="controlNo"
141
+ label-property="name"
142
+ description-property="description"
143
+ ></ox-tree-vertical>
144
+
145
+ <div editor>
146
+ <department-view
147
+ .department=${this.department}
148
+ @property-change=${(e: CustomEvent) => {
149
+ const { controlNo, name } = e.detail || {}
150
+
151
+ this.modified = true
152
+ this.appendable = controlNo && controlNo !== this.selected?.controlNo && name !== this.selected?.name
153
+ }}
154
+ ></department-view>
155
+ <div class="footer">
156
+ <button @click=${this.reset.bind(this)}><md-icon>restart_alt</md-icon>${i18next.t('button.reset')}</button>
157
+ <div filler></div>
158
+ <button @click=${this.create.bind(this)} ?disabled=${!this.appendable}>
159
+ <md-icon>add</md-icon>${i18next.t('button.add')}
160
+ </button>
161
+ <button @click=${this.save.bind(this)} ?disabled=${!this.modified} done>
162
+ <md-icon>save</md-icon>${i18next.t('button.save')}
163
+ </button>
164
+ </div>
165
+ </div>
166
+ </content>
167
+ `
168
+ }
169
+
170
+ updated(changes: PropertyValues<this>) {
171
+ if (changes.has('selected')) {
172
+ this.modified = false
173
+ this.department = { ...this.selected }
174
+ }
175
+ }
176
+
177
+ onSelect(e: CustomEvent) {
178
+ this.selected = e.detail as Department
179
+ this.updateContext()
180
+ }
181
+
182
+ reset() {
183
+ this.departmentView.department = {}
184
+ }
185
+
186
+ async create() {
187
+ const { id: parentId } = this.department || {}
188
+ const { controlNo, name, description, picture, active, manager } = this.department || {}
189
+
190
+ var department = {
191
+ controlNo,
192
+ name,
193
+ description,
194
+ manager,
195
+ active
196
+ } as any
197
+
198
+ if ((picture as any) instanceof File) {
199
+ department.picture = picture
200
+ }
201
+
202
+ if (parentId) {
203
+ department.parent = { id: parentId }
204
+ }
205
+
206
+ const response = await client.mutate({
207
+ mutation: gql`
208
+ mutation ($department: NewDepartment!) {
209
+ createDepartment(department: $department) {
210
+ ...SubDepartmentFragment
211
+ }
212
+ }
213
+
214
+ ${SubDepartmentFragment}
215
+ `,
216
+ variables: {
217
+ department
218
+ },
219
+ context: {
220
+ hasUpload: true
221
+ }
222
+ })
223
+
224
+ this.selected = response.data.createDepartment
225
+ this.updateContext()
226
+
227
+ await this.fetch()
228
+ }
229
+
230
+ async save() {
231
+ const { id, controlNo, name, description, picture, active, manager } = this.department || {}
232
+
233
+ if (!id) {
234
+ await CustomAlert({
235
+ type: 'warning',
236
+ title: 'department not selected',
237
+ text: 'Please select department first.',
238
+ confirmButton: { text: i18next.t('button.confirm') }
239
+ })
240
+
241
+ return
242
+ }
243
+
244
+ var patch = {
245
+ controlNo,
246
+ name,
247
+ description,
248
+ active,
249
+ manager
250
+ } as any
251
+
252
+ if ((picture as any) instanceof File) {
253
+ patch.picture = picture
254
+ }
255
+
256
+ const response = await client.mutate({
257
+ mutation: gql`
258
+ mutation ($id: String!, $patch: DepartmentPatch!) {
259
+ updateDepartment(id: $id, patch: $patch) {
260
+ ...SubDepartmentFragment
261
+ }
262
+ }
263
+
264
+ ${SubDepartmentFragment}
265
+ `,
266
+ variables: {
267
+ id,
268
+ patch
269
+ },
270
+ context: {
271
+ hasUpload: true
272
+ }
273
+ })
274
+
275
+ this.selected = response.data.updateDepartment
276
+
277
+ this.fetch()
278
+ }
279
+
280
+ async delete() {
281
+ if (!this.selected) {
282
+ await CustomAlert({
283
+ type: 'warning',
284
+ title: 'department not selected',
285
+ text: 'Please select department first.',
286
+ confirmButton: { text: i18next.t('button.confirm') }
287
+ })
288
+
289
+ return
290
+ }
291
+
292
+ const children = this.selected?.children
293
+ if (children && children.length > 0) {
294
+ await CustomAlert({
295
+ type: 'warning',
296
+ title: 'Departments with subordinates cannot be deleted.',
297
+ text: 'Department having children cannot be deleted.',
298
+ confirmButton: { text: i18next.t('button.confirm') }
299
+ })
300
+
301
+ return
302
+ }
303
+
304
+ if (confirm(i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }))) {
305
+ const { id } = this.selected || {}
306
+
307
+ const response = await client.mutate({
308
+ mutation: gql`
309
+ mutation ($id: String!) {
310
+ deleteDepartment(id: $id)
311
+ }
312
+ `,
313
+ variables: {
314
+ id
315
+ }
316
+ })
317
+
318
+ this.selected = {}
319
+ this.updateContext()
320
+
321
+ await this.fetch()
322
+ }
323
+ }
324
+
325
+ async pageInitialized(lifecycle: any) {
326
+ this.fetch()
327
+ }
328
+
329
+ async pageUpdated(changes: any, lifecycle: any) {
330
+ if (this.active) {
331
+ // do something here when this page just became as active
332
+ }
333
+ }
334
+
335
+ async fetch() {
336
+ const response = await client.query({
337
+ query: gql`
338
+ query {
339
+ responses: departmentRoots {
340
+ total
341
+ items {
342
+ ...SubDepartmentFragment
343
+ children {
344
+ ...SubDepartmentFragment
345
+ children {
346
+ ...SubDepartmentFragment
347
+ children {
348
+ ...SubDepartmentFragment
349
+ children {
350
+ ...SubDepartmentFragment
351
+ children {
352
+ ...SubDepartmentFragment
353
+ children {
354
+ ...SubDepartmentFragment
355
+ children {
356
+ ...SubDepartmentFragment
357
+ }
358
+ }
359
+ }
360
+ }
361
+ }
362
+ }
363
+ }
364
+ }
365
+ }
366
+ }
367
+
368
+ ${SubDepartmentFragment}
369
+ `
370
+ })
371
+ const { items: records, total } = response.data.responses
372
+
373
+ this.root = records[0]
374
+ }
375
+
376
+ async exportHandler() {}
377
+
378
+ async importHandler(records) {}
379
+ }
@@ -0,0 +1,87 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@operato/data-grist'
3
+
4
+ import gql from 'graphql-tag'
5
+ import { css, html, LitElement } from 'lit'
6
+ import { property } from 'lit/decorators.js'
7
+
8
+ import { client } from '@operato/graphql'
9
+ import { i18next } from '@operato/i18n'
10
+ import { isMobileDevice } from '@operato/utils'
11
+ import { CommonHeaderStyles } from '@operato/styles'
12
+
13
+ export class EmployeeImporter extends LitElement {
14
+ static styles = [
15
+ CommonHeaderStyles,
16
+ css`
17
+ :host {
18
+ display: flex;
19
+ flex-direction: column;
20
+
21
+ background-color: var(--md-sys-color-surface);
22
+ }
23
+
24
+ ox-grist {
25
+ flex: 1;
26
+ }
27
+ `
28
+ ]
29
+
30
+ @property({ type: Array }) employees: any[] = []
31
+ @property({ type: Object }) columns = {
32
+ list: { fields: ['name', 'description'] },
33
+ pagination: { infinite: true },
34
+ columns: [
35
+ {
36
+ type: 'string',
37
+ name: 'name',
38
+ header: i18next.t('field.name'),
39
+ width: 150
40
+ },
41
+ {
42
+ type: 'string',
43
+ name: 'description',
44
+ header: i18next.t('field.description'),
45
+ width: 200
46
+ },
47
+ {
48
+ type: 'checkbox',
49
+ name: 'active',
50
+ header: i18next.t('field.active'),
51
+ width: 60
52
+ }
53
+ ]
54
+ }
55
+
56
+ render() {
57
+ return html`
58
+ <ox-grist
59
+ .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
60
+ .config=${this.columns}
61
+ .data=${{
62
+ records: this.employees
63
+ }}
64
+ ></ox-grist>
65
+
66
+ <div class="footer">
67
+ <div filler></div>
68
+ <button @click=${this.save.bind(this)} done><md-icon>save</md-icon>${i18next.t('button.save')}</button>
69
+ </div>
70
+ `
71
+ }
72
+
73
+ async save() {
74
+ const response = await client.mutate({
75
+ mutation: gql`
76
+ mutation importEmployees($employees: [EmployeePatch!]!) {
77
+ importEmployees(employees: $employees)
78
+ }
79
+ `,
80
+ variables: { employees: this.employees }
81
+ })
82
+
83
+ if (response.errors?.length) return
84
+
85
+ this.dispatchEvent(new CustomEvent('imported'))
86
+ }
87
+ }