diff --git a/backend/test/modules/crm/push.test.ts b/backend/test/modules/crm/push.test.ts index af89fae..70e9c10 100644 --- a/backend/test/modules/crm/push.test.ts +++ b/backend/test/modules/crm/push.test.ts @@ -1,6 +1,7 @@ import { CrmRadarPushService, RadarPushPayload, + isHotRadarPush, } from '../../../src/modules/crm/service/push'; import { RadarTrack } from '../../../src/comm/types/omni'; @@ -91,6 +92,16 @@ describe('Task 35: 雷达 Push 通知内存渠道 (CrmRadarPushService)', () => expect(cardB[0].visitorId).toBe('vis_b'); }); + it('停留满 3 分钟或浏览 3 个模块视为高意向推送', () => { + expect(isHotRadarPush({ stayDurationMs: 179999, viewedBlocks: ['hero'] })).toBe( + false + ); + expect(isHotRadarPush({ stayDurationMs: 180000, viewedBlocks: [] })).toBe(true); + expect( + isHotRadarPush({ stayDurationMs: 1000, viewedBlocks: ['hero', 'case', 'article'] }) + ).toBe(true); + }); + it('unsubscribe 后不再收到后续 publish', () => { const received: RadarPushPayload[] = []; const listener = (payload: RadarPushPayload) => { diff --git a/backend/test/modules/crm/radar.test.ts b/backend/test/modules/crm/radar.test.ts index 3f0de80..d3a5226 100644 --- a/backend/test/modules/crm/radar.test.ts +++ b/backend/test/modules/crm/radar.test.ts @@ -262,6 +262,50 @@ describe('Task 11: 后端访客雷达模块 (modules/crm) 单元测试', () => { expect(result.country).toBe('Singapore'); expect(result.city).toBe('Singapore'); }); + + it('高停留足迹会走雷达 Push,普通足迹不推', async () => { + const published: any[] = []; + radarService.crmRadarPushService = { + publish: (track: any) => { + published.push(track); + return track; + }, + } as any; + + await radarService.recordTrack({ + cardId: 101, + stayDurationMs: 5000, + viewedBlocks: ['hero'], + }); + await radarService.recordTrack({ + cardId: 101, + stayDurationMs: 180000, + viewedBlocks: ['hero'], + }); + + expect(published).toHaveLength(1); + expect(published[0].stayDurationMs).toBe(180000); + }); + }); + + describe('P2 高意向雷达收件箱', () => { + it('hotInbox 只统计当前用户名片的高意向足迹', async () => { + const radarService = new CrmRadarService(); + radarService.cardInfoEntity = { + find: jest.fn(async () => [{ id: 101, userId: 11 }]), + } as any; + radarService.crmTrackEntity = { + find: jest.fn(async () => [ + { id: 1, cardId: 101, stayDurationMs: 180000, viewedBlocks: [] }, + { id: 2, cardId: 101, stayDurationMs: 1000, viewedBlocks: ['hero'] }, + { id: 3, cardId: 202, stayDurationMs: 200000, viewedBlocks: [] }, + ]), + } as any; + + const inbox = await radarService.hotInbox(11); + expect(inbox.unread).toBe(1); + expect(inbox.list[0].cardId).toBe(101); + }); }); // ========================================== diff --git a/mpuniapp/common/types.uts b/mpuniapp/common/types.uts index 2dcea22..438fe56 100644 --- a/mpuniapp/common/types.uts +++ b/mpuniapp/common/types.uts @@ -87,7 +87,37 @@ export type LeadStatus = 'new' | 'contacted' | 'hot' | 'won' | 'archived'; export type LeadSource = 'nfc' | 'qr' | 'link' | 'ai_chat' | 'exchange'; -export type CardContactSource = 'exchange' | 'collect' | 'scan'; +export type CardContactSource = 'exchange' | 'collect' | 'scan' | 'event'; + +export type CardEventStatus = 'active' | 'ended'; + +export interface CardEvent { + id?: string | number; + code: string; + name: string; + status: CardEventStatus; +} + +export interface CardEventMember { + id?: string | number; + eventId: number; + userId: number; + cardId: number; +} + +export interface CardEventJoinResult { + event: CardEvent; + member: CardEventMember; +} + +export interface TeamRankItem { + memberId: number; + memberName: string; + leads: number; + touches: number; + score: number; + rank: number; +} export interface CardContact { id?: string | number; @@ -147,6 +177,11 @@ export interface RadarTrack { createdAt?: string | Date; } +export interface RadarHotInbox { + unread: number; + list: RadarTrack[]; +} + export interface RecordRadarTrackParams { cardId: string | number; visitorId: string;