Bỏ qua để đến nội dung

Intake pipeline

Ghi vào hàng đợi — không cần biết trước nguồn

Phần tiêu đề “Ghi vào hàng đợi — không cần biết trước nguồn”

Route POST /contacts/intakes (backend) — hoặc POST /api/intakes qua BFF — nhận:

backend/extensions/contacts/src/endpoints/controllers/intakes.controller.ts:30-54
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ừ Contacts
await 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.

backend/extensions/contacts/src/endpoints/controllers/intakes.controller.ts:71-105
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 resolveTagsForWrite

overrides (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.