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

Thêm automation condition/action mới

  • Thư mụcbackend/extensions/helpdesk/src/endpoints/services/automation/
    • conditions.ts evaluateCondition (switch theo field → switch theo op) + evaluateConditions (AND)
    • actions.ts runAction (switch theo action.type)
    • engine.ts load rule active theo trigger, runGuarded (in-flight guard), tăng run_count
  • Thư mụcadmin/packages/helpdesk/app/components/helpdesk/workflows/
    • automation-rule-edit-slideover.vue form rule — embed 2 builder dưới
    • condition-builder.vue / action-builder.vue list wrapper + nút Add
    • condition-row.vue / action-row.vue field/operator/value picker thật

Condition có shape cố định:

services/automation/conditions.ts:10-14
export interface AutomationCondition {
field: string;
op: string;
value: unknown;
}

Dispatch 2 tầng — switch theo field, mỗi field có switch riêng theo op. Case mẫu (priority):

services/automation/conditions.ts:78-85
case 'priority':
switch (op) {
case 'eq': return convPriority === String(value);
case 'neq': return convPriority !== String(value);
case 'in': return Array.isArray(value) && (value as string[]).includes(convPriority);
case 'not_in': return Array.isArray(value) && !(value as string[]).includes(convPriority);
default: return false;
}

evaluateConditions AND toàn bộ mảng — rule không có điều kiện thì luôn match:

services/automation/conditions.ts:159-162
export function evaluateConditions(conditions: AutomationCondition[], ctx: ConditionContext): boolean {
if (!conditions.length) return true;
return conditions.every((cond) => evaluateCondition(cond, ctx));
}

Field hiện có: inbox_id, priority, status, content, label_ids, assignee_id, business_hours, channel_type.

  1. Thêm case '<field_moi>': vào switch outer trong evaluateCondition(), viết switch op riêng theo khuôn priority ở trên (copy đúng shape eq/neq/in/not_in, hoặc kiểu khác nếu field không phải enum — xem case content dùng text-match op).

  2. Wire vào UI — mở condition-row.vue, thêm entry vào FIELD_OPTIONS:

    app/components/helpdesk/workflows/condition-row.vue:13-22
    const FIELD_OPTIONS = [
    { label: "Inbox", value: "inbox_id" },
    { label: "Priority", value: "priority" },
    { label: "Status", value: "status" },
    { label: "Message content", value: "content" },
    { label: "Labels", value: "label_ids" },
    { label: "Assignee", value: "assignee_id" },
    { label: "Business hours", value: "business_hours" },
    { label: "Channel type", value: "channel_type" },
    // { label: "Tên hiển thị", value: "<field_moi>" },
    ]
  3. Thêm nhánh vào opOptions (chọn tập operator phù hợp — enum dùng SELECT_OPS, text dùng TEXT_OPS, giờ làm việc dùng BH_OPS, hoặc khai <FIELD_MOI>_OPS riêng):

    condition-row.vue:31-43
    const SELECT_OPS = [
    { label: "is", value: "eq" },
    { label: "is not", value: "neq" },
    { label: "is one of", value: "in" },
    { label: "is not one of", value: "not_in" },
    ]
    const opOptions = computed(() => {
    switch (props.modelValue.field) {
    case "content": return TEXT_OPS
    case "business_hours": return BH_OPS
    default: return SELECT_OPS
    }
    })
  4. Nếu field mới cần widget value khác text đơn giản (dropdown, JSON…), thêm nhánh trong valueType và reset default op trong onFieldChange — cả 2 đều nằm trong condition-row.vue.

services/automation/actions.ts:14-17
export interface AutomationAction {
type: 'assign' | 'label' | 'set_priority' | 'set_status' | 'send_template' | 'reply';
params: Record<string, unknown>;
}

Case mẫu — ghi thẳng qua ItemsService (assign):

services/automation/actions.ts:39-46
case 'assign': {
const agentId = params.agent_id as string | number | undefined;
if (agentId != null && agentId !== '') {
const convSvc = await createItemsService(ctx, 'hd_conversations', accountability);
await convSvc.updateOne(convId, { assignee_id: Number(agentId) });
}
break;
}

Case mẫu — thao tác trực tiếp junction table qua Knex (label, không qua ItemsService vì cần add/remove idempotent):

services/automation/actions.ts:48-68
case 'label': {
const labelId = params.label_id as string | number | undefined;
const op = (params.op as string) ?? 'add';
if (labelId == null || labelId === '') break;
const labelIdNum = Number(labelId);
if (op === 'add') {
const existing = await ctx.database('hd_conversation_labels')
.where({ hd_conversations_id: convId, hd_labels_id: labelIdNum })
.first();
if (!existing) {
await ctx.database('hd_conversation_labels')
.insert({ hd_conversations_id: convId, hd_labels_id: labelIdNum });
}
} else if (op === 'remove') {
await ctx.database('hd_conversation_labels')
.where({ hd_conversations_id: convId, hd_labels_id: labelIdNum })
.del();
}
break;
}
  1. Mở rộng union AutomationAction['type'] với type mới.

  2. Thêm case '<type_moi>': vào switch trong runAction() — dùng khuôn assign (ghi qua ItemsService, có permission/activity/event) nếu action chỉ update field đơn giản trên conversation; dùng khuôn label (Knex trực tiếp) nếu action đụng junction table hoặc cần logic idempotent riêng.

  3. Wire vào UI — thêm entry vào ACTION_TYPE_OPTIONS:

    app/components/helpdesk/workflows/action-row.vue:13-20
    const ACTION_TYPE_OPTIONS = [
    { label: "Assign agent", value: "assign" },
    { label: "Add/remove label", value: "label" },
    { label: "Set priority", value: "set_priority" },
    { label: "Set status", value: "set_status" },
    { label: "Send template", value: "send_template" },
    { label: "Reply", value: "reply" },
    // { label: "Tên hiển thị", value: "<type_moi>" },
    ]
  4. Thêm block <template v-else-if="modelValue.type === '<type_moi>'"> render param picker cần thiết, gọi patchParam('<param_key>', $event) cho từng param — param_key phải khớp đúng tên params.<key> mà case backend đọc.