feat: add CRM lead assignment strategies

This commit is contained in:
Codex
2026-06-29 20:51:24 +08:00
parent f6710ace8b
commit ec23cd3b9b
10 changed files with 375 additions and 20 deletions

View File

@@ -6789,11 +6789,28 @@ async function testReferralAndCrmGrowth() {
formName: '集成测试客资',
examType: '专升本',
delaySec: 1,
assignmentMode: 'round_robin',
assignmentPool: [TENANT_SALES_USER_ID, TENANT_AGENT_USER_ID],
},
});
assert.equal(crmConfig.item?.enabled, true, 'tenant admin should enable CRM config');
assert.equal(crmConfig.item?.assignmentMode, 'round_robin', 'CRM config should persist lead assignment mode');
assert.deepEqual(crmConfig.item?.assignmentPool, [TENANT_SALES_USER_ID, TENANT_AGENT_USER_ID], 'CRM config should persist assignment pool');
assert.ok(!JSON.stringify(crmConfig).includes('crm-secret-smoke'), 'CRM config response should not leak secret');
const invalidCrmAssignment = await request('/api/crm/config', {
userId: TENANT_ADMIN_USER_ID,
method: 'PUT',
body: {
enabled: true,
url: 'https://crm.example.test/webhook',
assignmentMode: 'direct',
assignmentPool: [PARTNER_TENANT_ADMIN_USER_ID],
},
expectStatus: 400,
});
assert.equal(invalidCrmAssignment.code, 'CRM_ASSIGNMENT_POOL_INVALID', 'CRM assignment pool must reject cross-tenant users');
const tracked = await request('/api/referral/track-event', {
userId: false,
method: 'POST',
@@ -6808,6 +6825,8 @@ async function testReferralAndCrmGrowth() {
assert.equal(tracked.item?.refUserId, TENANT_SALES_USER_ID, 'tracking event should resolve referrer');
assert.equal(tracked.lead?.bound, true, 'first referral event should bind lead');
assert.equal(tracked.lead?.item?.referrerUserId, TENANT_SALES_USER_ID, 'lead should be bound to sales user');
assert.equal(tracked.lead?.item?.assignedToUserId, TENANT_SALES_USER_ID, 'round-robin should assign first lead to first sales member');
assert.equal(tracked.lead?.assignment?.userId, TENANT_SALES_USER_ID, 'track response should expose assignment assignee');
assert.ok(tracked.crmQueue?.id, 'first lead binding should enqueue CRM task');
const secondBind = await request('/api/referral/bind', {
@@ -6853,6 +6872,7 @@ async function testReferralAndCrmGrowth() {
});
assert.equal(manual.lead?.bound, true, 'tenant admin should be able to force manual bind');
assert.equal(manual.lead?.item?.referrerUserId, TENANT_AGENT_USER_ID, 'manual bind should move lead to agent');
assert.equal(manual.lead?.item?.assignedToUserId, TENANT_AGENT_USER_ID, 'round-robin should assign second lead to next pool member');
const qrcode = await request('/api/referral/qrcode', {
userId: TENANT_AGENT_USER_ID,
@@ -7082,6 +7102,7 @@ async function testReferralAndCrmGrowth() {
query: { status: 'pending' },
});
assert.ok(crmQueue.items?.some(item => item.leadId === manual.lead.item.id || item.leadId === tracked.lead.item.id), 'CRM queue should include referral lead task');
assert.ok(crmQueue.items?.some(item => item.payload?.assignee?.id === TENANT_AGENT_USER_ID), 'CRM queue payload should include assigned follow-up user');
assert.ok(!JSON.stringify(crmQueue).includes('crm-secret-smoke'), 'CRM queue should not leak secret');
const partnerReferralDenied = await request('/api/referral/sales-stats', {