source_module
Định danh module gửi tới (vd "helpdesk", "sales", "web_form").
Mặc định "manual" nếu không truyền — dùng để lọc theo nguồn ở UI triage.
Route POST /contacts/intakes (backend) — hoặc POST /api/intakes qua BFF — nhận:
export function create(ctx: AppContext) { return async (req: any, res: any) => { const body = req.body;
if (!body.email?.trim() && !body.phone?.trim()) { return res.status(400).send({ errors: [{ message: 'email or phone is required' }] }); }
const svc = await createItemsService(ctx, 'c_contact_intakes', req.accountability); const id = await svc.createOne({ name: body.name, email: body.email, phone: body.phone, source_module: body.source_module || 'manual', source_ref: body.source_ref, source_label: body.source_label, raw_payload: body.raw_payload ?? {}, status: 'pending', suggested_type: body.suggested_type, }); // ... };}Ràng buộc duy nhất: phải có email hoặc phone — mọi thứ khác tuỳ chọn.
source_module
Định danh module gửi tới (vd "helpdesk", "sales", "web_form").
Mặc định "manual" nếu không truyền — dùng để lọc theo nguồn ở UI triage.
source_ref
Tham chiếu ngược về bản ghi gốc phía module gửi (vd
"helpdesk:conv-201") — không phải FK thật, chỉ là chuỗi tự do để
người triage lần ngược nếu cần.
source_label
Mô tả người-đọc-được hiển thị ở UI triage (vd
"Email — support@company.com").
raw_payload
JSON tự do giữ nguyên dữ liệu gốc — không parse/validate cấu trúc bên trong, chỉ hiển thị lại cho người triage xem chi tiết.
// Từ bất kỳ extension/module nào khác — không cần import gì từ Contactsawait fetch("/api/intakes", { method: "POST", body: JSON.stringify({ name: "Marcus Chen", email: "marcus.chen@globaltech.io", phone: "+1 415 555 0199", source_module: "helpdesk", source_ref: "helpdesk:conv-201", source_label: "Email — support@company.com", raw_payload: { subject: "Pricing inquiry — enterprise tier" }, suggested_type: "lead", }),})Đây chính xác là cách Helpdesk sẽ tích hợp nếu muốn tự động tạo intake khi có khách lạ nhắn tin (hiện Helpdesk dùng contact-choke-point riêng để tự tạo contact ngay, không qua intake — intake dành cho trường hợp cần con người xác nhận trước khi tạo).
| Route | Method | Hành động |
|---|---|---|
/contacts/intakes/:id/match |
POST | Gắn intake với contact có sẵn — status: matched, lưu matched_contact_id |
/contacts/intakes/:id/promote |
POST | Tạo contact mới từ dữ liệu intake — status: promoted, lưu promoted_contact_id |
/contacts/intakes/:id/dismiss |
POST | Đánh dấu bỏ qua — status: dismissed, lưu lý do vào notes |
Cả ba đều ghi triaged_at (timestamp) + triaged_by (odp_users.id) — biết ai xử lý và khi nào.
const displayName = overrides.display_name || intake.name || intake.email || intake.phone || 'Untitled';
const contactPayload: Record<string, any> = { type: overrides.type || 'person', display_name: displayName, primary_email: overrides.primary_email || intake.email || '', primary_phone: overrides.primary_phone || intake.phone || '', status: overrides.status || 'contacts:active',};// suggested_type (nếu có) resolve qua resolveTypesForWrite → gắn types// tags (nếu overrides.tags có) resolve qua resolveTagsForWriteoverrides (body của request) luôn thắng dữ liệu gốc từ intake — cho phép người triage sửa tên/email/loại quan hệ trước khi tạo contact chính thức, không bị khoá cứng theo dữ liệu thô.
Sau khi tạo contact, createPrimaryRecords() (service dùng chung với luồng tạo contact thường) tạo luôn bản ghi c_contact_emails/phones is_primary: true — promote một intake đi qua đúng con đường tạo contact chuẩn, không có nhánh riêng.
c_contact_intakes