feat: add student crm push

This commit is contained in:
Codex
2026-06-30 16:21:17 +08:00
parent d94b20b8ed
commit 7f97b52165
15 changed files with 774 additions and 27 deletions

View File

@@ -145,6 +145,64 @@ function compactLeadPayload(task: CrmQueueRow, configRow: CrmConfigRow | null) {
};
}
function arrayValue(value: unknown) {
return Array.isArray(value) ? value : [];
}
function compactStudentCrmPushPayload(task: CrmQueueRow, configRow: CrmConfigRow | null) {
const payload = objectValue(task.payload);
const student = objectValue(payload.student);
const assignee = objectValue(payload.assignee);
const classInfo = objectValue(payload.class);
const followup = objectValue(payload.followup);
const classes = arrayValue(student.classes).slice(0, 5).map(item => {
const classItem = objectValue(item);
return {
classId: stringValue(classItem.classId),
className: stringValue(classItem.className),
classCode: stringValue(classItem.classCode),
};
});
return {
formName: stringValue(payload.formName, configRow?.formName || '刷题题库'),
examType: stringValue(payload.examType, configRow?.examType || '成人本科'),
source: stringValue(payload.source, task.source || 'manual_batch'),
title: stringValue(payload.title, '学生跟进提醒'),
message: stringValue(payload.message),
student: {
id: stringValue(student.userId) || stringValue(student.id, task.recordId || ''),
username: stringValue(student.username),
name: stringValue(student.name) || stringValue(student.username) || '未填写',
phone: stringValue(student.phone),
email: stringValue(student.email),
avatarPreset: stringValue(student.avatarPreset, 'male'),
regionName: stringValue(student.regionName),
selectedSchoolName: stringValue(student.selectedSchoolName),
selectedMajorName: stringValue(student.selectedMajorName),
classes,
},
assignee: stringValue(assignee.userId) || stringValue(assignee.id) ? {
id: stringValue(assignee.userId) || stringValue(assignee.id),
name: stringValue(assignee.name) || stringValue(assignee.username),
role: stringValue(assignee.role),
} : null,
class: stringValue(classInfo.classId) ? {
classId: stringValue(classInfo.classId),
className: stringValue(classInfo.className),
classCode: stringValue(classInfo.classCode),
} : null,
followup: {
id: stringValue(followup.id),
title: stringValue(followup.title) || stringValue(payload.title),
followupType: stringValue(followup.followupType),
priority: stringValue(followup.priority),
dueAt: stringValue(followup.dueAt),
},
metadata: objectValue(payload.metadata),
};
}
function leadMarkdown(task: CrmQueueRow, configRow: CrmConfigRow | null) {
const payload = compactLeadPayload(task, configRow);
const student = payload.student;
@@ -162,6 +220,36 @@ function leadMarkdown(task: CrmQueueRow, configRow: CrmConfigRow | null) {
return lines.join('\n');
}
function studentCrmPushMarkdown(task: CrmQueueRow, configRow: CrmConfigRow | null) {
const payload = compactStudentCrmPushPayload(task, configRow);
const student = payload.student;
const target = [student.regionName, student.selectedSchoolName, student.selectedMajorName].filter(Boolean).join(' / ');
const classes = student.classes
.map(item => item.className || item.classCode)
.filter(Boolean)
.join('、');
const lines = [
`### ${payload.formName}学生跟进`,
`- 考试类型:${payload.examType}`,
`- 来源:${payload.source}`,
`- 任务:${payload.title}`,
`- 学生:${student.name}`,
`- 手机:${student.phone || '未填写'}`,
`- 意向:${target || '未填写'}`,
`- 班级:${payload.class?.className || classes || '无'}`,
`- 指派:${payload.assignee?.name || payload.assignee?.id || '未指派'}`,
`- 优先级:${payload.followup.priority || 'normal'}`,
`- 到期时间:${payload.followup.dueAt || '无'}`,
`- 跟进ID${payload.followup.id || '无'}`,
];
if (payload.message) lines.splice(4, 0, `- 说明:${payload.message}`);
return lines.join('\n');
}
function taskEventType(task: CrmQueueRow) {
return stringValue(objectValue(task.payload).eventType, 'lead.created');
}
function dingtalkSign(secret: string): Record<string, string> {
if (!secret) return {};
const timestamp = String(Date.now());
@@ -193,7 +281,10 @@ function prepareRequest(task: CrmQueueRow, configRow: CrmConfigRow | null, secre
const target = validateWebhookUrl(task.targetUrl || configRow?.url || '');
const provider = providerFrom(configRow, task);
const secretValue = secretText(secret, ['secret', 'signSecret', 'webhookSecret']);
const markdown = leadMarkdown(task, configRow);
const eventType = taskEventType(task);
const isStudentPush = eventType === 'student.crm_push';
const markdown = isStudentPush ? studentCrmPushMarkdown(task, configRow) : leadMarkdown(task, configRow);
const title = isStudentPush ? '学生跟进提醒' : '新客资提醒';
const headers = { 'content-type': 'application/json' };
if (provider === 'dingtalk') {
@@ -204,7 +295,7 @@ function prepareRequest(task: CrmQueueRow, configRow: CrmConfigRow | null, secre
body: {
msgtype: 'markdown',
markdown: {
title: '新客资提醒',
title,
text: markdown,
},
},
@@ -221,7 +312,7 @@ function prepareRequest(task: CrmQueueRow, configRow: CrmConfigRow | null, secre
...feishuSign(secretValue),
card: {
config: { wide_screen_mode: true },
header: { title: { tag: 'plain_text', content: '新客资提醒' }, template: 'blue' },
header: { title: { tag: 'plain_text', content: title }, template: isStudentPush ? 'wathet' : 'blue' },
elements: [{ tag: 'markdown', content: markdown }],
},
},
@@ -244,7 +335,10 @@ function prepareRequest(task: CrmQueueRow, configRow: CrmConfigRow | null, secre
provider,
url: target.toString(),
headers,
body: {
body: isStudentPush ? {
event: 'student.crm_push',
data: compactStudentCrmPushPayload(task, configRow),
} : {
event: 'lead.created',
data: compactLeadPayload(task, configRow),
},