Data model
Toàn bộ collection
Phần tiêu đề “Toàn bộ collection”Thứ tự khớp sort trong schema — cũng là thứ tự tạo khi Setup Wizard chạy (bảng phụ thuộc FK nằm sau bảng nó tham chiếu).
| # | Collection | Loại | Mô tả |
|---|---|---|---|
| 1 | hr |
label (không có bảng) | Thư mục gom nhóm trong Data Studio |
| 2 | hr_people |
table | Bảng trung tâm — satellite của c_contacts; talent/candidate/employee đều là 1 row ở đây, phân biệt bằng stage |
| 3 | hr_departments |
table | Phòng ban, có head_person_id và parent_department_id tự tham chiếu (cây phân cấp) |
| 4 | hr_employment_types |
table (lookup, seeded) | full_time / part_time / contractor / intern |
| 5 | hr_stage_transitions |
table | Audit trail đổi stage, ghi from_stage/to_stage/performed_by |
| 6 | hr_applications |
table | Một vòng ứng tuyển của một hr_people (1 người có thể có nhiều application qua các đợt) |
| 7 | hr_interviews |
table | Buổi phỏng vấn, gắn với 1 application |
| 8 | hr_offers |
table | Offer 1:1 với application (lương, hạn, trạng thái draft/sent/accepted/declined/expired) |
| 9 | hr_contracts |
table | Hợp đồng lao động (indefinite/fixed_term/probation/freelance) |
| 10 | hr_compensations |
table | Lương/thu nhập hiện tại của 1 người |
| 11 | hr_compensation_allowances |
table | Phụ cấp O2M của 1 compensation |
| 12 | hr_insurances |
table | Số BHXH/BHYT + trạng thái đủ điều kiện |
| 13 | hr_leave_types |
table (lookup, seeded) | annual / sick / unpaid / maternity / paternity |
| 14 | hr_leave_balances |
table | Số ngày phép còn lại theo người + loại + năm |
| 15 | hr_leave_requests |
table | Đơn xin nghỉ phép (pending/approved/rejected/cancelled) |
| 16 | hr_company_holidays |
table | Ngày nghỉ lễ công ty (có thể lặp hằng năm) |
| 17 | hr_documents |
table | Tài liệu của 1 người (hợp đồng, offer letter, ID, tài liệu tự sinh) |
| 18 | hr_document_templates |
table | Mẫu tài liệu (markdown/rich-text) |
| 19 | hr_document_triggers |
table | Sự kiện tự sinh tài liệu (offer_sent, hired, probation_confirmed, resigned…) |
| 20 | hr_document_trigger_tpl_jn |
junction (M2M) | Nối hr_document_triggers ↔ hr_document_templates — 1 trigger có thể sinh nhiều template |
| 21 | hr_assets |
table | Tài sản cấp phát (laptop, phone, access card…), có images (files nhiều ảnh) |
| 22 | hr_notes |
table | Ghi chú nội bộ theo người |
| 23 | hr_policies |
table | Rule tính leave/tenure-bonus/eligibility/probation, params JSON |
| 24 | hr_settings |
singleton | Cấu hình toàn module (tên công ty, currency, năm tài chính, nhãn stage tuỳ biến) |
| 25 | hr_providers |
table | Provider 3rd-party (Authentik/webhook) cho provisioning tài khoản |
| 26 | hr_provisioning_logs |
table | Log provision/deprovision/update tài khoản theo người + provider |
| 27 | hr_offboarding |
table | Case nghỉ việc (exit type, notice date, settlement, exit interview) |
| 28 | hr_offboarding_tasks |
table | Checklist bàn giao O2M của 1 offboarding case |
Collection dùng chung tham chiếu tới nhưng không thuộc HR:
c_contacts— master identity, HR là satellite bắt buộc (xem Tích hợp Contacts)odp_users— liên kết tài khoản đăng nhập (hr_people.user_id),performed_by/interviewer/approved_by/author_id/assigneetrên các bảng conodp_access/odp_policies/odp_app_permissions— mô hình app-permission (module:action), xem Kiến trúc tổng thể
Cấu trúc thư mục schema nguồn
Phần tiêu đề “Cấu trúc thư mục schema nguồn”Thư mụcadmin/packages/hr/
- app/data/hr-schema.ts ModuleSchema — 28 collection + field + relation + seed, chạy qua Setup Wizard
Bảng trung tâm: hr_people
Phần tiêu đề “Bảng trung tâm: hr_people”| Field | Type | Ghi chú |
|---|---|---|
contact_id |
integer, is_unique: true |
M2O → c_contacts — satellite 1:1, không được trùng contact |
stage |
string, required, default talent |
6 giá trị: talent → interviewing → offer → probation → active → resigned |
employee_id |
string, unique | Sinh tự động (generateNextId) khi vào stage probation/active, không sinh cho stage recruitment |
first_name/last_name/display_name/email/phone |
string | Cache identity — backfill một lần từ c_contacts lúc tạo trong people.service.ts#createPerson, không đồng bộ nền liên tục sau đó |
user_id |
uuid, M2O → odp_users |
Liên kết tài khoản ODP — dùng cho self-service (chưa làm, xem PERMISSIONS.md Phase 3) và Dept-Manager row-scoping |
department_id |
integer, M2O → hr_departments |
|
employment_type_id |
integer, M2O → hr_employment_types |
Đổi field này kích hoạt hook recalculatePersonBalances |
Quan hệ chính
Phần tiêu đề “Quan hệ chính”| Từ | Field | Tới | Ghi chú |
|---|---|---|---|
hr_people |
contact_id |
c_contacts |
nullify — 1:1 master/satellite |
hr_people |
department_id |
hr_departments |
nullify |
hr_people |
employment_type_id |
hr_employment_types |
nullify |
hr_people |
user_id |
odp_users |
nullify |
hr_departments |
head_person_id |
hr_people |
self-loop khác bảng — dùng cho Dept-Manager scoping |
hr_departments |
parent_department_id |
hr_departments |
self-reference — cây phân cấp |
hr_stage_transitions, hr_applications, hr_contracts, hr_compensations, hr_insurances, hr_leave_balances, hr_leave_requests, hr_documents, hr_assets, hr_notes, hr_provisioning_logs, hr_offboarding |
person_id |
hr_people |
O2M, on_delete: "CASCADE" khai báo ở schema (DB-level) |
hr_interviews |
application_id |
hr_applications |
O2M cascade |
hr_offers |
application_id |
hr_applications |
1:1 cascade |
hr_compensation_allowances |
compensation_id |
hr_compensations |
O2M cascade |
hr_leave_balances/hr_leave_requests |
leave_type_id |
hr_leave_types |
nullify |
hr_documents |
template_id |
hr_document_templates |
nullify |
hr_offboarding_tasks |
offboarding_id |
hr_offboarding |
O2M cascade |
hr_provisioning_logs |
provider_id |
hr_providers |
nullify |
Junction M2M duy nhất: hr_document_trigger_tpl_jn
Phần tiêu đề “Junction M2M duy nhất: hr_document_trigger_tpl_jn”Khác Helpdesk (3 junction, đều hydrate bằng raw Knex whereIn + Map), HR chỉ có một junction thật (hr_document_trigger_tpl_jn, nối hr_document_triggers ↔ hr_document_templates), và cách quản lý cũng khác về chi tiết: triggers.controller.ts build template_ids bằng một query readByQuery rồi .filter() trong JS (không phải Map gộp theo id như Helpdesk, vì số lượng trigger nhỏ):
const triggers = await svc.readByQuery({ sort: ['id'] });const mappings = await jnSvc.readByQuery({ fields: ['hr_document_triggers_id', 'hr_document_templates_id'], limit: -1,});
const mapped = triggers.map((t: any) => ({ ...t, template_ids: mappings .filter((m: any) => m.hr_document_triggers_id === t.id) .map((m: any) => m.hr_document_templates_id),}));Ghi (PATCH) cũng theo nguyên tắc “xoá hết rồi insert lại tập đích”, giống syncConversationLabels của Helpdesk — không để ItemsService.updateOne tự M2M-sync mảng id:
if (Array.isArray(template_ids)) { const existing = await jnSvc.readByQuery({ filter: { hr_document_triggers_id: { _eq: req.params.id } }, fields: ['id'], limit: -1, }); if (existing.length > 0) { await jnSvc.deleteMany(existing.map((e: any) => e.id)); } if (template_ids.length > 0) { await jnSvc.createMany( template_ids.map((tid: number) => ({ hr_document_triggers_id: req.params.id, hr_document_templates_id: tid, })) ); }}Hai tầng cascade delete — không hoàn toàn trùng khớp
Phần tiêu đề “Hai tầng cascade delete — không hoàn toàn trùng khớp”Xoá một hr_people chạy qua hai cơ chế song song, không phải một:
- App-level, filter hook (
hooks/index.ts→cascadeDeletePerson,services/cascade-delete.ts) — chạy TRƯỚC khi rowhr_peoplethật sự bị xoá, xoá tường minh 11 bảng con quaDIRECT_CHILD_COLLECTIONS:hr_notes,hr_stage_transitions,hr_leave_balances,hr_leave_requests,hr_documents,hr_assets,hr_provisioning_logs,hr_applications(kéo theohr_interviews/hr_offersquaapplication_id),hr_contracts,hr_compensations(kéo theohr_compensation_allowances),hr_insurances. - Schema-level
on_delete: "CASCADE"khai báo trongrelationscủahr-schema.ts— DB tự cascade khi FK constraint được tạo thật ở tầng DB.
Đọc tiếp
Phần tiêu đề “Đọc tiếp”- Kiến trúc tổng thể — bức tranh 4 tầng, choke point tạo person
- Tích hợp Contacts — chi tiết master/satellite + HR tab
- Mở rộng HR — thêm field/collection mới