Mở rộng module HR
Vị trí mã nguồn
Phần tiêu đề “Vị trí mã nguồn”Thư mụcadmin/packages/hr/app/
- data/hr-schema.ts khai báo collection + field + relation (setup wizard, new install)
- composables/use-hr-field-registry.ts fetch + normalize field metadata theo danh sách collection tự chọn
- config/form-layouts.ts hằng số layout viết tay (TALENT_FORM_LAYOUT, EMPLOYEE_FORM_LAYOUT, CONTRACT_LAYOUT, COMPENSATION_LAYOUT, INSURANCE_LAYOUT…)
- config/column-renderers.ts renderer cột cho bảng danh sách
- utils/hr-permissions.ts khai báo action module
hr(đăng ký quadefineLayerPermissions)
Thư mụcbackend/extensions/hr/src/endpoints/
- controllers/meta.controller.ts
COLLECTION_MAPchoGET /hr/fields/:collection
- controllers/meta.controller.ts
Bước 1 — Khai báo field trong hr-schema.ts (new install)
Phần tiêu đề “Bước 1 — Khai báo field trong hr-schema.ts (new install)”Field là object phẳng { collection, field, type, meta, schema }, tách khỏi entry đăng ký collection — ví dụ field text đơn giản trên hr_people:
{ collection: "hr_people", field: "job_title", type: "string", meta: { interface: "input", width: "half" }, schema: {},},Field JSON cần editor code (ví dụ tham số policy):
{ collection: "hr_policies", field: "params", type: "json", meta: { interface: "input-code", special: ["cast-json"], options: { language: "json" }, width: "full" }, schema: {},},Bước 2 — DB đã có sẵn: tạo field qua Data Studio / API
Phần tiêu đề “Bước 2 — DB đã có sẵn: tạo field qua Data Studio / API”Nếu module đã chạy production, tạo field trực tiếp qua Data Studio (hoặc API POST /fields/:collection của ODP core) — hr-schema.ts chỉ dùng để tài liệu hoá / cho lần cài mới sau.
Bước 3 — Field-metadata endpoint: COLLECTION_MAP, không phải allowlist chặn cứng
Phần tiêu đề “Bước 3 — Field-metadata endpoint: COLLECTION_MAP, không phải allowlist chặn cứng”meta.controller.ts chỉ có một bảng ánh xạ short name → tên collection thật, dùng cho 6 collection hay gọi từ form:
const COLLECTION_MAP: Record<string, string> = { people: 'hr_people', departments: 'hr_departments', employment_types: 'hr_employment_types', leave_types: 'hr_leave_types', policies: 'hr_policies', settings: 'hr_settings',};
export function fields(ctx: AppContext) { return async (req: any, res: any) => { const { collection } = req.params; const realCollection = COLLECTION_MAP[collection] ?? collection; // ... const result = await fieldsService.readAll(realCollection); return res.send({ data: result }); };}Bước 4 — Client field-registry: tự chọn danh sách, không có COLLECTIONS cố định
Phần tiêu đề “Bước 4 — Client field-registry: tự chọn danh sách, không có COLLECTIONS cố định”Khác helpdesk (use-helpdesk-field-registry.ts có hằng COLLECTIONS cố định 11 phần tử), useHrFieldRegistry() của HR nhận danh sách collection làm tham số mỗi lần gọi:
export function useHrFieldRegistry(collections: string[]) { const api = useHrApi() const registry = ref<Record<string, Field[]>>({}) // fetchAll() gọi api.getFields(c) cho từng collection trong `collections`}Chuẩn hoá chỉ có 2 việc: đổi select-dropdown → select, và tính lại width (half/full) thành col-span cho grid 24-cột của FormRoot (vì Field.vue mặc định col-span-full, chỉ tôn trọng meta.options.ui.base):
const WIDTH_COLSPAN: Record<string, string> = { 'half': '@xl:col-span-12', 'half-left': '@xl:col-span-12', 'half-right': '@xl:col-span-12',}Bước 5 — Wire vào form: layout viết tay là chuẩn, không phải mảng tên field
Phần tiêu đề “Bước 5 — Wire vào form: layout viết tay là chuẩn, không phải mảng tên field”Phần lớn form HR không dùng kiểu “layout khai tên field, registry tự render” như helpdesk. app/config/form-layouts.ts định nghĩa layout bằng hằng số mô tả chi tiết UI cho từng field — kiểu input, options, điều kiện hiển thị:
export const CONTRACT_LAYOUT = { sections: [ { key: 'contract_details', label: 'contract.sections.details', fields: [ { key: 'contract_type', type: 'select', options: ['Indefinite', 'Definite'], required: true }, { key: 'contract_number', type: 'text', required: true }, { key: 'start_date', type: 'date', required: true }, { key: 'end_date', type: 'date', conditional: (form: any) => form.contract_type === 'Definite' }, { key: 'probation_end', type: 'date', readonly: true }, { key: 'renewal_count', type: 'number', min: 0 }, ], }, ],} as constCOMPENSATION_LAYOUT còn có type: 'dynamic_list' cho danh sách phụ cấp (itemFields), và field có thể tham chiếu nguồn ngoài (source: 'settings.currency'). Thêm field mới vào layout này nghĩa là thêm object vào đúng fields/itemFields — không chỉ tên field như helpdesk, mà cả type, options, conditional nếu cần.
Thêm sub-collection hr_* mới hoàn toàn
Phần tiêu đề “Thêm sub-collection hr_* mới hoàn toàn”-
Thêm entry collection (
meta.group: "hr",sortđúng vị trí theo phụ thuộc FK — xem thứ tự 28 collection hiện có ở Data model) + field + relation vàohr-schema.ts. -
Thêm controller mới trong
backend/extensions/hr/src/endpoints/controllers/. HR không cócrud.factory.tsdùng chung như helpdesk — mỗi controller hiện có (departments.controller.ts,holidays.controller.ts,assets.controller.ts…) tự viết bằngItemsServicetrực tiếp; controller mới nên mirror controller gần giống nhất về shape dữ liệu (vd một bảng con theoperson_idthì mirrornotes.controller.tshoặcassets.controller.ts). -
Đăng ký route trong
endpoints/routes.ts, bọc bằng guard quyền tương ứng (xem Kiến trúc tổng thể cho chi tiết guard/withAccess). -
Thêm route BFF proxy trong
admin/packages/hr/server/api/hr/<name>/+ composable client trongapp/composables/. -
Nếu form cần field metadata, thêm short-name vào
COLLECTION_MAP(Bước 3 ở trên).
Thêm action/permission mới
Phần tiêu đề “Thêm action/permission mới”Action module hr khai báo tập trung trong hr-permissions.ts — mỗi action là 1 object { key, displayName, description?, riskLevel }:
{ key: 'leave.approve.dept', displayName: 'Approve Leave (own department)', description: 'Approve leave only for members of departments the user heads', riskLevel: 'medium' },Thêm action mới ở đây là điều kiện cần nhưng chưa đủ — action còn phải:
- Được backend thực sự kiểm tra ở route guard (xem
withAccess/validateAppAccesstrong Kiến trúc tổng thể). - Được seed vào ít nhất 1 policy trong
hr-policy-seed-step.ts(HR Admin/HR Manager/HR Recruiter/HR Viewer) nếu muốn có sẵn theo role mặc định — nếu không, admin phải tự thêm quyền tay qua Settings → Permissions/Roles. Chi tiết ma trận role→action hiện có ở Thiết lập hệ thống.
Đọc tiếp
Phần tiêu đề “Đọc tiếp”- Kiến trúc tổng thể — 4 tầng, guard quyền, trust model
- Data model — toàn bộ 28 collection
hr_*và thứ tự FK - Tích hợp Contacts (Master/Satellite) — vì sao
hr_peoplekhông tự lưu identity - Frontend (Nuxt layer + BFF) — composable + BFF surface đầy đủ