Thêm field vào collection có sẵn
Vị trí mã nguồn
Phần tiêu đề “Vị trí mã nguồn”Thư mụcadmin/packages/helpdesk/app/
- data/helpdesk-schema.ts khai báo collection + field (setup wizard, new install)
- composables/use-helpdesk-field-registry.ts COLLECTIONS allowlist + normalize + tweak
- config/form-layouts.ts layout khai báo field theo tab/section (dùng cho registry-driven form)
Thư mụcbackend/extensions/helpdesk/src/endpoints/controllers/
- meta.controller.ts ALLOWED allowlist cho GET /fields/:collection
Bước 1 — Khai báo field trong helpdesk-schema.ts (new install)
Phần tiêu đề “Bước 1 — Khai báo field trong helpdesk-schema.ts (new install)”Field là một object phẳng { collection, field, type, meta, schema }, khai báo tách rời khỏi entry đăng ký collection. Ví dụ 2 field text trên hd_labels:
{ collection: "hd_labels", field: "name", type: "string", meta: { interface: "input", width: "half", required: true }, schema: { is_nullable: false },},{ collection: "hd_labels", field: "color", type: "string", meta: { interface: "input", width: "half" }, schema: {},},Field kiểu M2M (alias, không có cột thật):
{ collection: "hd_teams", field: "agent_ids", type: "alias", meta: { interface: "list-m2m", special: ["m2m"], width: "full" },},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) thay vì sửa schema — schema chỉ dùng để tài liệu hoá / cho lần cài mới sau.
Bước 3 — Field cần lộ ra qua field-metadata endpoint?
Phần tiêu đề “Bước 3 — Field cần lộ ra qua field-metadata endpoint?”Backend chặn bằng allowlist cứng — collection không nằm trong danh sách này trả 404 Unknown collection, không phải lỗi im lặng:
const ALLOWED = new Set<string>([ 'hd_inboxes', 'hd_agents', 'hd_teams', 'hd_sla_policies', 'hd_working_hours', 'hd_automation_rules', 'hd_canned_responses', 'hd_templates', 'hd_saved_views', 'hd_connections', 'hd_settings', 'hd_conversations', 'hd_messages', 'c_contacts',]);
export function fields(ctx: AppContext) { return async (req: any, res: any) => { const { collection } = req.params; if (!ALLOWED.has(collection)) { return res.status(404).send({ errors: [{ message: 'Unknown collection' }] }); } // ... };}Nếu field thuộc 1 trong 13 collection đã allowlist thì không cần đụng gì ở đây. Collection mới hoàn toàn (không nằm trong 13 cái này) → xem Thêm sub-collection hd_* mới.
Bước 4 — Client field-registry: 11 collection, không phải 13
Phần tiêu đề “Bước 4 — Client field-registry: 11 collection, không phải 13”Client tự giới hạn thêm xuống còn 11 collection nó thực sự build form:
const COLLECTIONS = [ "hd_inboxes", "hd_agents", "hd_teams", "hd_sla_policies", "hd_working_hours", "hd_automation_rules", "hd_canned_responses", "hd_templates", "hd_saved_views", "hd_connections", "hd_conversations",] as consthd_settings, hd_messages, c_contacts được backend allow (dùng cho việc khác — m2o hiển thị agent contact, singleton settings) nhưng registry client không build form từ chúng.
Registry chuẩn hoá interface (v.d. select-dropdown → select) và có bảng tweak riêng cho từng field:
const INTERFACE_NORMALIZE: Record<string, string> = { "select-dropdown": "select",}
const FIELD_TWEAKS: Partial<Record<CollectionKey, Record<string, { interface?: string, options?: Record<string, any>, readonly?: boolean }>>> = { hd_inboxes: { signature: { interface: "input-rich-text-html" }, }, hd_agents: { // m2o to c_contacts — show contact display name contact_id: { options: { template: "{{display_name}}" } }, }, hd_working_hours: { timezone: { interface: "select" }, schedule: { interface: "input-code", options: { language: "json" } }, holidays: { interface: "input-code", options: { language: "json" } }, }, hd_sla_policies: { applies_to: { interface: "input-code", options: { language: "json" } }, }, hd_automation_rules: { conditions: { interface: "input-code", options: { language: "json" } }, actions: { interface: "input-code", options: { language: "json" } }, }, hd_saved_views: { filters: { interface: "input-code", options: { language: "json" } }, },}Field JSON mới (kiểu hd_sla_policies.applies_to) hầu như chắc chắn cần thêm entry interface: "input-code", options: { language: "json" } ở đây, nếu không registry sẽ render nó bằng input mặc định theo meta.interface thô từ backend.
Bước 5 — Wire vào form
Phần tiêu đề “Bước 5 — Wire vào form”Có 2 khả năng tuỳ collection:
-
Form dùng registry (layout khai báo trong
app/config/form-layouts.ts) — chỉ cần thêm tên field vào mảngfieldscủa section liên quan:app/config/form-layouts.ts:5-20 — INBOX_FORM_LAYOUT export const INBOX_FORM_LAYOUT: FormLayout = {tabs: [{id: "basic",labelKey: "helpdesk.form.tabs.basic",sections: [{ id: "identity", grid: 2, fields: ["name", "channel_type"] },{ id: "status", grid: 1, fields: ["is_active"] },],},// ...],}Thêm field mới vào đúng
fields: [...]— không cần sửa gì khác trong file này. -
Form viết tay (inline) — đa số form helpdesk (composer, SLA policy, automation rule, canned response/template, label, inbox detail) cố ý không dùng
FormRoot/registry vì UX bespoke (autocomplete, variable picker, condition builder…). Field mới phải wire trực tiếp vào component Vue của form đó (thêm input + bind vào payload gửi PATCH/POST).
Đọc tiếp
Phần tiêu đề “Đọc tiếp”- Thêm sub-collection hd_* mới — khi field cần đi kèm cả bảng mới
- Data model — toàn bộ collection
hd_*hiện có - Frontend (Nuxt layer + BFF) — bức tranh composable/BFF tổng thể