@codihaus/odp-app-hr 0.1.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.
- package/app/components/hr/contact/tab.vue +238 -0
- package/app/components/hr/department/card.vue +141 -0
- package/app/components/hr/department/form-modal.vue +90 -0
- package/app/components/hr/employees/assets/tab.vue +432 -0
- package/app/components/hr/employees/compensation.vue +136 -0
- package/app/components/hr/employees/contract.vue +77 -0
- package/app/components/hr/employees/insurance.vue +164 -0
- package/app/components/hr/employees/leave/tab.vue +180 -0
- package/app/components/hr/employees/provisioning/form-modal.vue +219 -0
- package/app/components/hr/employees/provisioning/tab.vue +187 -0
- package/app/components/hr/employees/tab.vue +38 -0
- package/app/components/hr/leave/calendar-tab.vue +649 -0
- package/app/components/hr/leave/override-modal.vue +62 -0
- package/app/components/hr/leave/request-modal.vue +185 -0
- package/app/components/hr/leave/requests-tab.vue +289 -0
- package/app/components/hr/leave/timeline-tab.vue +259 -0
- package/app/components/hr/offboarding/tab.vue +303 -0
- package/app/components/hr/person/activity/tab.vue +65 -0
- package/app/components/hr/person/activity/timeline.vue +119 -0
- package/app/components/hr/person/detail.vue +303 -0
- package/app/components/hr/person/document/tab-documents.vue +120 -0
- package/app/components/hr/person/document/template-edit-drawer.vue +215 -0
- package/app/components/hr/person/document/template-preview-card.vue +39 -0
- package/app/components/hr/person/document/trigger-modal.vue +121 -0
- package/app/components/hr/person/employee-form-modal.vue +78 -0
- package/app/components/hr/person/form-modal.vue +78 -0
- package/app/components/hr/person/list-row.vue +40 -0
- package/app/components/hr/person/profile/tab.vue +231 -0
- package/app/components/hr/settings/automation.vue +113 -0
- package/app/components/hr/settings/documents.vue +200 -0
- package/app/components/hr/settings/general.vue +87 -0
- package/app/components/hr/settings/holidays.vue +171 -0
- package/app/components/hr/settings/integrations.vue +185 -0
- package/app/components/hr/settings/policies.vue +83 -0
- package/app/components/hr/settings/policy/benefit-override-modal.vue +59 -0
- package/app/components/hr/settings/policy/editor-eligibility.vue +27 -0
- package/app/components/hr/settings/policy/editor-leave-base.vue +37 -0
- package/app/components/hr/settings/policy/editor-tenure-bonus.vue +61 -0
- package/app/components/hr/settings/recruitment.vue +128 -0
- package/app/components/hr/settings/taxonomies.vue +170 -0
- package/app/components/hr/shared/row.vue +21 -0
- package/app/components/hr/shared/section.vue +20 -0
- package/app/components/hr/shared/source-badge.vue +42 -0
- package/app/components/hr/shared/stage-badge.vue +24 -0
- package/app/components/hr/shared/workflow-timeline.vue +27 -0
- package/app/components/hr/talents/app-sidebar.vue +54 -0
- package/app/components/hr/talents/application-form-modal.vue +114 -0
- package/app/components/hr/talents/pipeline-picker.vue +56 -0
- package/app/components/hr/talents/step-detail.vue +133 -0
- package/app/components/hr/talents/step-stepper.vue +85 -0
- package/app/components/hr/talents/tab.vue +263 -0
- package/app/composables/use-departments.ts +59 -0
- package/app/composables/use-employee-detail.ts +24 -0
- package/app/composables/use-holidays.ts +48 -0
- package/app/composables/use-hr-api.ts +210 -0
- package/app/composables/use-hr-field-registry.ts +76 -0
- package/app/composables/use-hr-policies.ts +66 -0
- package/app/composables/use-hr-settings.ts +118 -0
- package/app/composables/use-leave.ts +71 -0
- package/app/composables/use-offboarding.ts +49 -0
- package/app/composables/use-people.ts +149 -0
- package/app/composables/use-providers.ts +44 -0
- package/app/composables/use-recruitment-workflow.ts +173 -0
- package/app/composables/use-templates.ts +44 -0
- package/app/composables/use-triggers.ts +26 -0
- package/app/config/column-renderers.ts +4 -0
- package/app/config/form-layouts.ts +193 -0
- package/app/data/hr-schema.ts +2608 -0
- package/app/lib/policy-engine.ts +116 -0
- package/app/pages/hr/departments.vue +114 -0
- package/app/pages/hr/employees/[id]/activity.vue +10 -0
- package/app/pages/hr/employees/[id]/assets.vue +14 -0
- package/app/pages/hr/employees/[id]/employment.vue +14 -0
- package/app/pages/hr/employees/[id]/index.vue +9 -0
- package/app/pages/hr/employees/[id]/offboarding.vue +7 -0
- package/app/pages/hr/employees/[id]/profile.vue +11 -0
- package/app/pages/hr/employees/[id]/provisioning.vue +17 -0
- package/app/pages/hr/employees/[id].vue +313 -0
- package/app/pages/hr/employees/index.vue +291 -0
- package/app/pages/hr/index.vue +3 -0
- package/app/pages/hr/leave.vue +79 -0
- package/app/pages/hr/settings.vue +43 -0
- package/app/pages/hr/setup.vue +3 -0
- package/app/pages/hr/talents/[id]/interview/[stepId].vue +231 -0
- package/app/pages/hr/talents/[id].vue +52 -0
- package/app/pages/hr/talents/index.vue +224 -0
- package/app/pages/hr.vue +129 -0
- package/app/plugins/hr-contacts-sync.client.ts +3 -0
- package/app/plugins/hr-extensions.ts +36 -0
- package/app/plugins/hr-setup.ts +5 -0
- package/app/plugins/navigations.ts +22 -0
- package/app/utils/hr-permissions.ts +27 -0
- package/app/utils/hr-policy-seed-step.ts +110 -0
- package/i18n/locales/en.json +726 -0
- package/i18n/locales/vi.json +688 -0
- package/nuxt.config.ts +19 -0
- package/package.json +27 -0
- package/server/api/hr/departments/[id].delete.ts +12 -0
- package/server/api/hr/departments/[id].patch.ts +14 -0
- package/server/api/hr/departments/index.get.ts +11 -0
- package/server/api/hr/departments/index.post.ts +13 -0
- package/server/api/hr/documents/templates/[id]/preview.post.ts +16 -0
- package/server/api/hr/documents/templates/[id].delete.ts +14 -0
- package/server/api/hr/documents/templates/[id].patch.ts +16 -0
- package/server/api/hr/documents/templates/index.get.ts +15 -0
- package/server/api/hr/documents/templates/index.post.ts +15 -0
- package/server/api/hr/documents/triggers/[id].patch.ts +16 -0
- package/server/api/hr/documents/triggers/index.get.ts +13 -0
- package/server/api/hr/fields/[collection].get.ts +14 -0
- package/server/api/hr/holidays/[id].delete.ts +14 -0
- package/server/api/hr/holidays/[id].patch.ts +16 -0
- package/server/api/hr/holidays/copy.post.ts +15 -0
- package/server/api/hr/holidays/index.get.ts +15 -0
- package/server/api/hr/holidays/index.post.ts +15 -0
- package/server/api/hr/leave/requests/[id].patch.ts +22 -0
- package/server/api/hr/leave/requests.get.ts +15 -0
- package/server/api/hr/leave/types.get.ts +13 -0
- package/server/api/hr/offboarding/[id]/cancel.post.ts +8 -0
- package/server/api/hr/offboarding/[id]/deprovision.post.ts +8 -0
- package/server/api/hr/offboarding/[id]/finalize.post.ts +8 -0
- package/server/api/hr/offboarding/[id]/return-assets.post.ts +8 -0
- package/server/api/hr/offboarding/[id]/settlement.get.ts +8 -0
- package/server/api/hr/offboarding/[id]/tasks/[taskId].patch.ts +10 -0
- package/server/api/hr/offboarding/[id].get.ts +8 -0
- package/server/api/hr/offboarding/[id].patch.ts +9 -0
- package/server/api/hr/offboarding/index.get.ts +7 -0
- package/server/api/hr/people/[id]/applications/[appId]/interviews/[iid].delete.ts +16 -0
- package/server/api/hr/people/[id]/applications/[appId]/interviews/[iid].patch.ts +18 -0
- package/server/api/hr/people/[id]/applications/[appId]/interviews/index.get.ts +15 -0
- package/server/api/hr/people/[id]/applications/[appId]/interviews/index.post.ts +17 -0
- package/server/api/hr/people/[id]/applications/[appId].patch.ts +17 -0
- package/server/api/hr/people/[id]/applications/index.get.ts +14 -0
- package/server/api/hr/people/[id]/applications/index.post.ts +16 -0
- package/server/api/hr/people/[id]/assets/[aid].delete.ts +13 -0
- package/server/api/hr/people/[id]/assets/[aid].patch.ts +15 -0
- package/server/api/hr/people/[id]/assets/index.get.ts +12 -0
- package/server/api/hr/people/[id]/assets/index.post.ts +14 -0
- package/server/api/hr/people/[id]/compensations.get.ts +14 -0
- package/server/api/hr/people/[id]/compensations.patch.ts +16 -0
- package/server/api/hr/people/[id]/contracts.get.ts +14 -0
- package/server/api/hr/people/[id]/contracts.patch.ts +16 -0
- package/server/api/hr/people/[id]/documents/[did].delete.ts +15 -0
- package/server/api/hr/people/[id]/documents/index.get.ts +14 -0
- package/server/api/hr/people/[id]/documents/index.post.ts +16 -0
- package/server/api/hr/people/[id]/insurances.get.ts +14 -0
- package/server/api/hr/people/[id]/insurances.patch.ts +16 -0
- package/server/api/hr/people/[id]/leave-balances/[bid].patch.ts +17 -0
- package/server/api/hr/people/[id]/leave-balances/index.get.ts +14 -0
- package/server/api/hr/people/[id]/leave-requests/index.get.ts +14 -0
- package/server/api/hr/people/[id]/leave-requests/index.post.ts +16 -0
- package/server/api/hr/people/[id]/link-user.post.ts +16 -0
- package/server/api/hr/people/[id]/notes/[nid].delete.ts +15 -0
- package/server/api/hr/people/[id]/notes/index.get.ts +14 -0
- package/server/api/hr/people/[id]/notes/index.post.ts +16 -0
- package/server/api/hr/people/[id]/offboarding/cases.get.ts +12 -0
- package/server/api/hr/people/[id]/offboarding.get.ts +12 -0
- package/server/api/hr/people/[id]/offboarding.post.ts +14 -0
- package/server/api/hr/people/[id]/provisioning/[logId]/retry.post.ts +7 -0
- package/server/api/hr/people/[id]/provisioning/index.get.ts +6 -0
- package/server/api/hr/people/[id]/provisioning/index.post.ts +7 -0
- package/server/api/hr/people/[id]/transition.post.ts +19 -0
- package/server/api/hr/people/[id]/transitions.get.ts +14 -0
- package/server/api/hr/people/[id].delete.ts +15 -0
- package/server/api/hr/people/[id].get.ts +14 -0
- package/server/api/hr/people/[id].patch.ts +17 -0
- package/server/api/hr/people/index.get.ts +15 -0
- package/server/api/hr/people/index.post.ts +19 -0
- package/server/api/hr/policies/[id].patch.ts +16 -0
- package/server/api/hr/policies/index.get.ts +13 -0
- package/server/api/hr/providers/[id]/test.post.ts +6 -0
- package/server/api/hr/providers/[id].delete.ts +6 -0
- package/server/api/hr/providers/[id].patch.ts +7 -0
- package/server/api/hr/providers/index.get.ts +5 -0
- package/server/api/hr/providers/index.post.ts +6 -0
- package/server/api/hr/settings/employment-types/[id].delete.ts +14 -0
- package/server/api/hr/settings/employment-types/[id].patch.ts +16 -0
- package/server/api/hr/settings/employment-types/index.get.ts +13 -0
- package/server/api/hr/settings/employment-types/index.post.ts +15 -0
- package/server/api/hr/settings/index.get.ts +13 -0
- package/server/api/hr/settings/index.patch.ts +15 -0
- package/server/api/hr/settings/leave-types/[id].delete.ts +14 -0
- package/server/api/hr/settings/leave-types/[id].patch.ts +16 -0
- package/server/api/hr/settings/leave-types/index.get.ts +13 -0
- package/server/api/hr/settings/leave-types/index.post.ts +15 -0
- package/shared/types/form-layout.ts +30 -0
- package/shared/types/index.ts +2 -0
- package/shared/types/integration.ts +41 -0
- package/shared/types/leave.ts +53 -0
- package/shared/types/offboarding.ts +46 -0
- package/shared/types/person.ts +54 -0
- package/shared/types/settings.ts +16 -0
- package/shared/utils/template-render.ts +155 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
export function useHrApi() {
|
|
2
|
+
const { $api } = useNuxtApp()
|
|
3
|
+
|
|
4
|
+
function unwrap<T>(res: T | { data: T }): T {
|
|
5
|
+
return (res && typeof res === 'object' && 'data' in res) ? (res as any).data : res
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function getSettings() {
|
|
9
|
+
return unwrap(await $api<HrSettings | { data: HrSettings }>('/hr/settings'))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function patchSettings(body: Partial<HrSettings>) {
|
|
13
|
+
return unwrap(await $api<HrSettings | { data: HrSettings }>('/hr/settings', { method: 'PATCH', body }))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function getFields(collection: string) {
|
|
17
|
+
return unwrap(await $api<any[] | { data: any[] }>(`/hr/fields/${collection}`))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// People CRUD
|
|
21
|
+
|
|
22
|
+
async function listPeople(query?: Record<string, string>) {
|
|
23
|
+
const res = await $api<any>('/hr/people', { query })
|
|
24
|
+
return { data: unwrap(res.data ?? res), meta: res.meta ?? null }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function getPerson(id: string) {
|
|
28
|
+
return unwrap(await $api<Person | { data: Person }>(`/hr/people/${id}`))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function createPerson(body: Partial<Person>) {
|
|
32
|
+
return unwrap(await $api<Person | { data: Person }>('/hr/people', { method: 'POST', body }))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function updatePerson(id: string, body: Partial<Person>) {
|
|
36
|
+
return unwrap(await $api<Person | { data: Person }>(`/hr/people/${id}`, { method: 'PATCH', body }))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function deletePerson(id: string) {
|
|
40
|
+
await $api(`/hr/people/${id}`, { method: 'DELETE' })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Notes
|
|
44
|
+
|
|
45
|
+
async function listNotes(personId: string) {
|
|
46
|
+
return unwrap(await $api<HrNote[] | { data: HrNote[] }>(`/hr/people/${personId}/notes`))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function createNote(personId: string, body: string) {
|
|
50
|
+
return unwrap(await $api<HrNote | { data: HrNote }>(`/hr/people/${personId}/notes`, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
body: { body },
|
|
53
|
+
}))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function deleteNote(personId: string, noteId: string) {
|
|
57
|
+
await $api(`/hr/people/${personId}/notes/${noteId}`, { method: 'DELETE' })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Leave
|
|
61
|
+
|
|
62
|
+
async function listLeaveTypes() {
|
|
63
|
+
return unwrap(await $api<any[] | { data: any[] }>(`/hr/settings/leave-types`))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function createLeaveRequest(personId: string, body: any) {
|
|
67
|
+
return unwrap(await $api<any | { data: any }>(`/hr/people/${personId}/leave-requests`, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
body,
|
|
70
|
+
}))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Contract
|
|
74
|
+
|
|
75
|
+
async function getContract(personId: string) {
|
|
76
|
+
return unwrap(await $api<any | { data: any }>(`/hr/people/${personId}/contracts`))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function updateContract(personId: string, body: any) {
|
|
80
|
+
return unwrap(await $api<any | { data: any }>(`/hr/people/${personId}/contracts`, {
|
|
81
|
+
method: 'PATCH',
|
|
82
|
+
body,
|
|
83
|
+
}))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Compensation
|
|
87
|
+
|
|
88
|
+
async function getCompensation(personId: string) {
|
|
89
|
+
return unwrap(await $api<any | { data: any }>(`/hr/people/${personId}/compensations`))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function updateCompensation(personId: string, body: any) {
|
|
93
|
+
return unwrap(await $api<any | { data: any }>(`/hr/people/${personId}/compensations`, {
|
|
94
|
+
method: 'PATCH',
|
|
95
|
+
body,
|
|
96
|
+
}))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Insurance
|
|
100
|
+
|
|
101
|
+
async function getInsurance(personId: string) {
|
|
102
|
+
return unwrap(await $api<any | { data: any }>(`/hr/people/${personId}/insurances`))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function updateInsurance(personId: string, body: any) {
|
|
106
|
+
return unwrap(await $api<any | { data: any }>(`/hr/people/${personId}/insurances`, {
|
|
107
|
+
method: 'PATCH',
|
|
108
|
+
body,
|
|
109
|
+
}))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Assets
|
|
113
|
+
|
|
114
|
+
async function listAssets(personId: string) {
|
|
115
|
+
return unwrap(await $api<any[] | { data: any[] }>(`/hr/people/${personId}/assets`))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function createAsset(personId: string, body: any) {
|
|
119
|
+
return unwrap(await $api<any | { data: any }>(`/hr/people/${personId}/assets`, {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
body,
|
|
122
|
+
}))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function updateAsset(personId: string, assetId: string | number, body: any) {
|
|
126
|
+
return unwrap(await $api<any | { data: any }>(`/hr/people/${personId}/assets/${assetId}`, {
|
|
127
|
+
method: 'PATCH',
|
|
128
|
+
body,
|
|
129
|
+
}))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function deleteAsset(personId: string, assetId: string | number) {
|
|
133
|
+
await $api(`/hr/people/${personId}/assets/${assetId}`, { method: 'DELETE' })
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Departments
|
|
137
|
+
|
|
138
|
+
async function listDepartments() {
|
|
139
|
+
return unwrap(await $api<any[] | { data: any[] }>('/hr/departments'))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function createDepartment(body: any) {
|
|
143
|
+
return unwrap(await $api<any | { data: any }>('/hr/departments', {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
body,
|
|
146
|
+
}))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function updateDepartment(id: number, body: any) {
|
|
150
|
+
return unwrap(await $api<any | { data: any }>(`/hr/departments/${id}`, {
|
|
151
|
+
method: 'PATCH',
|
|
152
|
+
body,
|
|
153
|
+
}))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function deleteDepartment(id: number) {
|
|
157
|
+
await $api(`/hr/departments/${id}`, { method: 'DELETE' })
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Documents
|
|
161
|
+
|
|
162
|
+
async function listDocuments(personId: string) {
|
|
163
|
+
return unwrap(await $api<any[] | { data: any[] }>(`/hr/people/${personId}/documents`))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function generateDocument(personId: string, templateId: number | string) {
|
|
167
|
+
return unwrap(await $api<any | { data: any }>(`/hr/people/${personId}/documents`, {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
body: { type: 'generate', template_id: templateId },
|
|
170
|
+
}))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function deleteDocument(personId: string, docId: number | string) {
|
|
174
|
+
await $api(`/hr/people/${personId}/documents/${docId}`, { method: 'DELETE' })
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Provisioning
|
|
178
|
+
|
|
179
|
+
async function getProvisioningStatus(personId: string) {
|
|
180
|
+
return unwrap(await $api<ProvisioningStatus[] | { data: ProvisioningStatus[] }>(`/hr/people/${personId}/provisioning`))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function triggerProvisioning(personId: string, action: 'create' | 'disable' | 'enable') {
|
|
184
|
+
return unwrap(await $api<any | { data: any }>(`/hr/people/${personId}/provisioning`, {
|
|
185
|
+
method: 'POST',
|
|
186
|
+
body: { action },
|
|
187
|
+
}))
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function retryProvisioning(personId: string, logId: string) {
|
|
191
|
+
return unwrap(await $api<any | { data: any }>(`/hr/people/${personId}/provisioning/${logId}/retry`, {
|
|
192
|
+
method: 'POST',
|
|
193
|
+
}))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
unwrap,
|
|
198
|
+
getSettings, patchSettings, getFields,
|
|
199
|
+
listPeople, getPerson, createPerson, updatePerson, deletePerson,
|
|
200
|
+
listNotes, createNote, deleteNote,
|
|
201
|
+
listLeaveTypes, createLeaveRequest,
|
|
202
|
+
getContract, updateContract,
|
|
203
|
+
getCompensation, updateCompensation,
|
|
204
|
+
getInsurance, updateInsurance,
|
|
205
|
+
listAssets, createAsset, updateAsset, deleteAsset,
|
|
206
|
+
listDepartments, createDepartment, updateDepartment, deleteDepartment,
|
|
207
|
+
listDocuments, generateDocument, deleteDocument,
|
|
208
|
+
getProvisioningStatus, triggerProvisioning, retryProvisioning,
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const INTERFACE_NORMALIZE: Record<string, string> = {
|
|
2
|
+
'select-dropdown': 'select',
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
// FormRoot renders fields in a 24-col @container grid. Field.vue hard-codes
|
|
6
|
+
// `col-span-full` and only honors `meta.options.ui.base`, so the DB `width`
|
|
7
|
+
// (half/full) is otherwise ignored. Map it to a responsive col-span here so
|
|
8
|
+
// half-width fields sit two-per-row on wide containers and stack on narrow.
|
|
9
|
+
const WIDTH_COLSPAN: Record<string, string> = {
|
|
10
|
+
'half': '@xl:col-span-12',
|
|
11
|
+
'half-left': '@xl:col-span-12',
|
|
12
|
+
'half-right': '@xl:col-span-12',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeField(field: Field): Field {
|
|
16
|
+
if (!field.meta) return field
|
|
17
|
+
let meta = field.meta
|
|
18
|
+
const iface = meta.interface
|
|
19
|
+
if (iface && INTERFACE_NORMALIZE[iface]) {
|
|
20
|
+
meta = { ...meta, interface: INTERFACE_NORMALIZE[iface] }
|
|
21
|
+
}
|
|
22
|
+
const colspan = WIDTH_COLSPAN[meta.width ?? 'full']
|
|
23
|
+
if (colspan && !meta.options?.ui?.base) {
|
|
24
|
+
meta = {
|
|
25
|
+
...meta,
|
|
26
|
+
options: {
|
|
27
|
+
...(meta.options ?? {}),
|
|
28
|
+
ui: { ...(meta.options?.ui ?? {}), base: colspan },
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return meta === field.meta ? field : { ...field, meta }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useHrFieldRegistry(collections: string[]) {
|
|
36
|
+
const api = useHrApi()
|
|
37
|
+
const registry = ref<Record<string, Field[]>>({})
|
|
38
|
+
const loading = ref(false)
|
|
39
|
+
|
|
40
|
+
async function fetchAll() {
|
|
41
|
+
loading.value = true
|
|
42
|
+
try {
|
|
43
|
+
const results = await Promise.all(
|
|
44
|
+
collections.map(c => api.getFields(c).then(fields => ({ c, fields })))
|
|
45
|
+
)
|
|
46
|
+
results.forEach(({ c, fields }) => {
|
|
47
|
+
registry.value[c] = (fields ?? []).map(normalizeField)
|
|
48
|
+
})
|
|
49
|
+
} finally {
|
|
50
|
+
loading.value = false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getField(collection: string, key: string): Field | null {
|
|
55
|
+
return registry.value[collection]?.find(f => f.field === key) ?? null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function pickFields(collection: string, names: string[]): Field[] {
|
|
59
|
+
const result: Field[] = []
|
|
60
|
+
let sortIndex = 0
|
|
61
|
+
for (const name of names) {
|
|
62
|
+
const f = getField(collection, name)
|
|
63
|
+
if (f) {
|
|
64
|
+
result.push({ ...f, meta: { ...f.meta!, sort: sortIndex } })
|
|
65
|
+
sortIndex++
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isReady(collection: string): boolean {
|
|
72
|
+
return collection in registry.value
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { registry, loading, fetchAll, getField, pickFields, isReady }
|
|
76
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Policy, PolicyKey, PolicyParams } from "../../shared/types"
|
|
2
|
+
|
|
3
|
+
export function useHrPolicies() {
|
|
4
|
+
const policies = useState<Policy[]>('hr:policies', () => [])
|
|
5
|
+
const isPending = ref(false)
|
|
6
|
+
|
|
7
|
+
async function fetchPolicies(): Promise<void> {
|
|
8
|
+
isPending.value = true
|
|
9
|
+
try {
|
|
10
|
+
const { useHrApi } = await import('./use-hr-api')
|
|
11
|
+
const api = useHrApi()
|
|
12
|
+
const res = await $fetch<{ data: Policy[] } | Policy[]>('/api/hr/policies')
|
|
13
|
+
policies.value = api.unwrap(res) as Policy[]
|
|
14
|
+
} finally {
|
|
15
|
+
isPending.value = false
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getPolicy(key: PolicyKey): Policy | undefined {
|
|
20
|
+
return policies.value.find(p => p.key === key)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function togglePolicy(key: PolicyKey, enabled: boolean): Promise<void> {
|
|
24
|
+
const policy = getPolicy(key)
|
|
25
|
+
if (!policy) return
|
|
26
|
+
isPending.value = true
|
|
27
|
+
try {
|
|
28
|
+
const res = await $fetch<{ data: Policy } | Policy>(`/api/hr/policies/${policy.id}`, {
|
|
29
|
+
method: 'PATCH',
|
|
30
|
+
body: { enabled },
|
|
31
|
+
})
|
|
32
|
+
const idx = policies.value.findIndex(p => p.key === key)
|
|
33
|
+
if (idx >= 0) {
|
|
34
|
+
const { useHrApi } = await import('./use-hr-api')
|
|
35
|
+
const api = useHrApi()
|
|
36
|
+
policies.value[idx] = api.unwrap(res) as Policy
|
|
37
|
+
}
|
|
38
|
+
} finally {
|
|
39
|
+
isPending.value = false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function updatePolicyParams(key: PolicyKey, params: PolicyParams): Promise<void> {
|
|
44
|
+
const policy = getPolicy(key)
|
|
45
|
+
if (!policy) return
|
|
46
|
+
isPending.value = true
|
|
47
|
+
try {
|
|
48
|
+
const res = await $fetch<{ data: Policy } | Policy>(`/api/hr/policies/${policy.id}`, {
|
|
49
|
+
method: 'PATCH',
|
|
50
|
+
body: { params },
|
|
51
|
+
})
|
|
52
|
+
const idx = policies.value.findIndex(p => p.key === key)
|
|
53
|
+
if (idx >= 0) {
|
|
54
|
+
const { useHrApi } = await import('./use-hr-api')
|
|
55
|
+
const api = useHrApi()
|
|
56
|
+
policies.value[idx] = api.unwrap(res) as Policy
|
|
57
|
+
}
|
|
58
|
+
} finally {
|
|
59
|
+
isPending.value = false
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
onMounted(fetchPolicies)
|
|
64
|
+
|
|
65
|
+
return { policies, getPolicy, togglePolicy, updatePolicyParams, isPending }
|
|
66
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { LifecycleStage } from "../../shared/types/person"
|
|
2
|
+
import type { LeaveType, EmploymentType, HrGeneralSettings } from "../../shared/types/settings"
|
|
3
|
+
import { LIFECYCLE_STAGES } from "../../shared/types/person"
|
|
4
|
+
|
|
5
|
+
export function useHrSettings() {
|
|
6
|
+
const leaveTypes = useState<LeaveType[]>('hr:leave-types', () => [])
|
|
7
|
+
const employmentTypes = useState<EmploymentType[]>('hr:employment-types', () => [])
|
|
8
|
+
const general = useState<HrGeneralSettings>('hr:general-settings', () => ({
|
|
9
|
+
company_name: '',
|
|
10
|
+
fiscal_year_start_month: 1,
|
|
11
|
+
default_currency: 'VND',
|
|
12
|
+
}))
|
|
13
|
+
const lifecycleLabels = useState<Record<LifecycleStage, string>>(
|
|
14
|
+
'hr:lifecycle-labels',
|
|
15
|
+
() => Object.fromEntries(LIFECYCLE_STAGES.map(s => [s, s])) as Record<LifecycleStage, string>,
|
|
16
|
+
)
|
|
17
|
+
const isPending = ref(false)
|
|
18
|
+
|
|
19
|
+
function unwrap<T>(res: T | { data: T }): T {
|
|
20
|
+
return (res && typeof res === 'object' && 'data' in res) ? (res as any).data : res
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function fetchSettings(): Promise<void> {
|
|
24
|
+
const res = await $fetch<HrGeneralSettings | { data: HrGeneralSettings }>('/api/hr/settings')
|
|
25
|
+
const data = unwrap(res)
|
|
26
|
+
if (data) Object.assign(general.value, data)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function save(payload: Partial<HrGeneralSettings>): Promise<void> {
|
|
30
|
+
isPending.value = true
|
|
31
|
+
try {
|
|
32
|
+
const res = await $fetch<HrGeneralSettings | { data: HrGeneralSettings }>('/api/hr/settings', {
|
|
33
|
+
method: 'PATCH',
|
|
34
|
+
body: payload,
|
|
35
|
+
})
|
|
36
|
+
const data = unwrap(res)
|
|
37
|
+
if (data) Object.assign(general.value, data)
|
|
38
|
+
} finally {
|
|
39
|
+
isPending.value = false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function fetchLeaveTypes(): Promise<void> {
|
|
44
|
+
const res = await $fetch<LeaveType[] | { data: LeaveType[] }>('/api/hr/settings/leave-types')
|
|
45
|
+
leaveTypes.value = unwrap(res) as LeaveType[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function addLeaveType(input: Omit<LeaveType, 'id' | 'is_archived'>): Promise<void> {
|
|
49
|
+
const res = await $fetch<LeaveType | { data: LeaveType }>('/api/hr/settings/leave-types', {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
body: input,
|
|
52
|
+
})
|
|
53
|
+
leaveTypes.value.push(unwrap(res) as LeaveType)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function updateLeaveType(id: string | number, patch: Partial<LeaveType>): Promise<void> {
|
|
57
|
+
const res = await $fetch<LeaveType | { data: LeaveType }>(`/api/hr/settings/leave-types/${id}`, {
|
|
58
|
+
method: 'PATCH',
|
|
59
|
+
body: patch,
|
|
60
|
+
})
|
|
61
|
+
const updated = unwrap(res) as LeaveType
|
|
62
|
+
const idx = leaveTypes.value.findIndex(t => t.id === id)
|
|
63
|
+
if (idx >= 0) leaveTypes.value[idx] = updated
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function archiveLeaveType(id: string | number): Promise<void> {
|
|
67
|
+
await updateLeaveType(id, { status: 'archived' } as any)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function fetchEmploymentTypes(): Promise<void> {
|
|
71
|
+
const res = await $fetch<EmploymentType[] | { data: EmploymentType[] }>('/api/hr/settings/employment-types')
|
|
72
|
+
employmentTypes.value = unwrap(res) as EmploymentType[]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function addEmploymentType(input: Omit<EmploymentType, 'id' | 'is_archived'>): Promise<void> {
|
|
76
|
+
const res = await $fetch<EmploymentType | { data: EmploymentType }>('/api/hr/settings/employment-types', {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
body: input,
|
|
79
|
+
})
|
|
80
|
+
employmentTypes.value.push(unwrap(res) as EmploymentType)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function updateEmploymentType(id: string | number, patch: Partial<EmploymentType>): Promise<void> {
|
|
84
|
+
const res = await $fetch<EmploymentType | { data: EmploymentType }>(`/api/hr/settings/employment-types/${id}`, {
|
|
85
|
+
method: 'PATCH',
|
|
86
|
+
body: patch,
|
|
87
|
+
})
|
|
88
|
+
const updated = unwrap(res) as EmploymentType
|
|
89
|
+
const idx = employmentTypes.value.findIndex(t => t.id === id)
|
|
90
|
+
if (idx >= 0) employmentTypes.value[idx] = updated
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function archiveEmploymentType(id: string | number): Promise<void> {
|
|
94
|
+
await $fetch(`/api/hr/settings/employment-types/${id}`, { method: 'DELETE' })
|
|
95
|
+
const idx = employmentTypes.value.findIndex(t => t.id === id)
|
|
96
|
+
if (idx >= 0) employmentTypes.value[idx] = { ...employmentTypes.value[idx]!, is_archived: true }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function setStageLabel(stage: LifecycleStage, label: string): void {
|
|
100
|
+
lifecycleLabels.value[stage] = label
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
onMounted(async () => {
|
|
104
|
+
await Promise.all([fetchSettings(), fetchLeaveTypes(), fetchEmploymentTypes()])
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
leaveTypes,
|
|
109
|
+
employmentTypes,
|
|
110
|
+
general,
|
|
111
|
+
lifecycleLabels,
|
|
112
|
+
isPending,
|
|
113
|
+
save,
|
|
114
|
+
addLeaveType, updateLeaveType, archiveLeaveType,
|
|
115
|
+
addEmploymentType, updateEmploymentType, archiveEmploymentType,
|
|
116
|
+
setStageLabel,
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export function useLeave() {
|
|
2
|
+
const leaveRequests = useState<LeaveRequest[]>('leave.requests', () => [])
|
|
3
|
+
const loading = useState('leave.loading', () => false)
|
|
4
|
+
|
|
5
|
+
const pendingLeave = computed(() => leaveRequests.value.filter(r => r.status?.toLowerCase() === 'pending'))
|
|
6
|
+
const approvedLeave = computed(() => leaveRequests.value.filter(r => r.status?.toLowerCase() === 'approved'))
|
|
7
|
+
const rejectedLeave = computed(() => leaveRequests.value.filter(r => r.status?.toLowerCase() === 'rejected'))
|
|
8
|
+
const totalDaysUsed = computed(() => approvedLeave.value.reduce((sum, r) => sum + (r.days_count ?? r.days ?? 0), 0))
|
|
9
|
+
const pendingCount = computed(() => pendingLeave.value.length)
|
|
10
|
+
|
|
11
|
+
const stats = computed(() => ({
|
|
12
|
+
pending: pendingLeave.value.length,
|
|
13
|
+
approved: approvedLeave.value.length,
|
|
14
|
+
rejected: rejectedLeave.value.length,
|
|
15
|
+
totalDays: totalDaysUsed.value,
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
const { unwrap } = useHrApi()
|
|
19
|
+
|
|
20
|
+
async function fetchRequests(params?: Record<string, string>) {
|
|
21
|
+
loading.value = true
|
|
22
|
+
try {
|
|
23
|
+
const res = await $fetch<any>('/api/hr/leave/requests', { query: params })
|
|
24
|
+
leaveRequests.value = unwrap(res?.data ?? res) ?? []
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
loading.value = false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function setStatus(id: string | number, status: LeaveRequest['status'], rejectReason?: string) {
|
|
32
|
+
const { unwrap: u } = useHrApi()
|
|
33
|
+
await $fetch(`/api/hr/leave/requests/${id}`, {
|
|
34
|
+
method: 'PATCH',
|
|
35
|
+
body: { status, reject_reason: rejectReason },
|
|
36
|
+
})
|
|
37
|
+
const idx = leaveRequests.value.findIndex(r => String(r.id) === String(id))
|
|
38
|
+
if (idx >= 0) {
|
|
39
|
+
leaveRequests.value[idx] = { ...leaveRequests.value[idx]!, status }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function createRequest(personId: string | number, payload: {
|
|
44
|
+
leave_type_id: number
|
|
45
|
+
start_date: string
|
|
46
|
+
end_date: string
|
|
47
|
+
reason?: string
|
|
48
|
+
}) {
|
|
49
|
+
const res = await $fetch<any>(`/api/hr/people/${personId}/leave-requests`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
body: payload,
|
|
52
|
+
})
|
|
53
|
+
const created: LeaveRequest = unwrap(res?.data ?? res)
|
|
54
|
+
leaveRequests.value = [created, ...leaveRequests.value]
|
|
55
|
+
return created
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
leaveRequests,
|
|
60
|
+
loading,
|
|
61
|
+
pendingLeave,
|
|
62
|
+
approvedLeave,
|
|
63
|
+
rejectedLeave,
|
|
64
|
+
totalDaysUsed,
|
|
65
|
+
pendingCount,
|
|
66
|
+
stats,
|
|
67
|
+
fetchRequests,
|
|
68
|
+
setStatus,
|
|
69
|
+
createRequest,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function useOffboarding() {
|
|
2
|
+
const { $api } = useNuxtApp()
|
|
3
|
+
const unwrap = (r: any) => (r && typeof r === 'object' && 'data' in r ? r.data : r)
|
|
4
|
+
// Shared signal so the Off-boarding tab re-fetches after an action fired
|
|
5
|
+
// from elsewhere (e.g. the Initiate modal in the detail navbar) even when
|
|
6
|
+
// the route doesn't change.
|
|
7
|
+
const refreshKey = useState('hr:offboarding:refresh', () => 0)
|
|
8
|
+
|
|
9
|
+
async function fetchForPerson(personId: string | number): Promise<Offboarding | null> {
|
|
10
|
+
return unwrap(await $api<any>(`/hr/people/${personId}/offboarding`))
|
|
11
|
+
}
|
|
12
|
+
async function listForPerson(personId: string | number): Promise<Offboarding[]> {
|
|
13
|
+
return unwrap(await $api<any>(`/hr/people/${personId}/offboarding/cases`)) ?? []
|
|
14
|
+
}
|
|
15
|
+
async function list(): Promise<Offboarding[]> {
|
|
16
|
+
return unwrap(await $api<any>(`/hr/offboarding`)) ?? []
|
|
17
|
+
}
|
|
18
|
+
async function get(id: string | number): Promise<Offboarding> {
|
|
19
|
+
return unwrap(await $api<any>(`/hr/offboarding/${id}`))
|
|
20
|
+
}
|
|
21
|
+
async function initiate(personId: string | number, body: Partial<Offboarding>): Promise<Offboarding> {
|
|
22
|
+
const created = unwrap(await $api<any>(`/hr/people/${personId}/offboarding`, { method: 'POST', body }))
|
|
23
|
+
refreshKey.value++
|
|
24
|
+
return created
|
|
25
|
+
}
|
|
26
|
+
async function updateCase(id: string | number, body: Partial<Offboarding>): Promise<Offboarding> {
|
|
27
|
+
return unwrap(await $api<any>(`/hr/offboarding/${id}`, { method: 'PATCH', body }))
|
|
28
|
+
}
|
|
29
|
+
async function updateTask(id: string | number, taskId: string | number, body: Partial<OffboardingTask>) {
|
|
30
|
+
return $api<any>(`/hr/offboarding/${id}/tasks/${taskId}`, { method: 'PATCH', body })
|
|
31
|
+
}
|
|
32
|
+
async function returnAssets(id: string | number) {
|
|
33
|
+
return unwrap(await $api<any>(`/hr/offboarding/${id}/return-assets`, { method: 'POST' }))
|
|
34
|
+
}
|
|
35
|
+
async function deprovision(id: string | number) {
|
|
36
|
+
return unwrap(await $api<any>(`/hr/offboarding/${id}/deprovision`, { method: 'POST' }))
|
|
37
|
+
}
|
|
38
|
+
async function getSettlement(id: string | number): Promise<OffboardingSettlement> {
|
|
39
|
+
return unwrap(await $api<any>(`/hr/offboarding/${id}/settlement`))
|
|
40
|
+
}
|
|
41
|
+
async function finalize(id: string | number): Promise<Offboarding> {
|
|
42
|
+
return unwrap(await $api<any>(`/hr/offboarding/${id}/finalize`, { method: 'POST' }))
|
|
43
|
+
}
|
|
44
|
+
async function cancel(id: string | number): Promise<Offboarding> {
|
|
45
|
+
return unwrap(await $api<any>(`/hr/offboarding/${id}/cancel`, { method: 'POST' }))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { refreshKey, fetchForPerson, listForPerson, list, get, initiate, updateCase, updateTask, returnAssets, deprovision, getSettlement, finalize, cancel }
|
|
49
|
+
}
|