feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口
补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。 Co-authored-by: Cursor <cursoragent@cursor.com>
这个提交包含在:
@@ -5,6 +5,10 @@ import { CrmLeadService } from '../../../src/modules/crm/service/lead';
|
||||
import { OpenCrmLeadController } from '../../../src/modules/crm/controller/open/lead';
|
||||
import { LeadEntity, LeadStatus } from '../../../src/comm/types/omni';
|
||||
import { CoolCommException } from '@cool-midway/core';
|
||||
import {
|
||||
AUDIT_ACTION_LEAD_BATCH_EXPORT,
|
||||
BaseAuditLogService,
|
||||
} from '../../../src/modules/base/service/audit';
|
||||
|
||||
describe('Task 12: 后端潜客线索模块 (modules/crm) 单元测试', () => {
|
||||
const metadata = getMetadataArgsStorage();
|
||||
@@ -333,4 +337,82 @@ describe('Task 12: 后端潜客线索模块 (modules/crm) 单元测试', () => {
|
||||
expect(response.code).not.toBe(1000);
|
||||
});
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// 5. 线索批量导出审计 (batchExport)
|
||||
// ==========================================
|
||||
describe('5. 线索批量导出成功后写入审计 (batchExport)', () => {
|
||||
let leadService: CrmLeadService;
|
||||
let savedLeads: CrmLeadEntity[] = [];
|
||||
let record: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
savedLeads = [
|
||||
Object.assign(new CrmLeadEntity(), {
|
||||
id: 1,
|
||||
name: 'Alice',
|
||||
phone: '+971-50-1111111',
|
||||
email: 'a@ex.com',
|
||||
status: 'new',
|
||||
source: 'nfc',
|
||||
}),
|
||||
Object.assign(new CrmLeadEntity(), {
|
||||
id: 2,
|
||||
name: 'Bob',
|
||||
phone: '+971-50-2222222',
|
||||
email: 'b@ex.com',
|
||||
status: 'hot',
|
||||
source: 'qr',
|
||||
}),
|
||||
];
|
||||
|
||||
record = jest.fn(async dto => dto);
|
||||
const auditService = new BaseAuditLogService();
|
||||
auditService.record = record as unknown as BaseAuditLogService['record'];
|
||||
|
||||
leadService = new CrmLeadService();
|
||||
leadService.baseAuditLogService = auditService;
|
||||
leadService.crmLeadEntity = {
|
||||
find: jest.fn().mockImplementation(async () => [...savedLeads]),
|
||||
save: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
} as any;
|
||||
leadService.baseCtx = {
|
||||
admin: { userId: 7, username: 'ExportAdmin' },
|
||||
} as unknown as CrmLeadService['baseCtx'];
|
||||
});
|
||||
|
||||
it('导出成功应记录 lead_batch_export 审计且正文仅含条数不含手机号', async () => {
|
||||
const result = await leadService.batchExport({});
|
||||
|
||||
expect(result.exportCount).toBe(2);
|
||||
expect(result.list).toHaveLength(2);
|
||||
expect(record).toHaveBeenCalledTimes(1);
|
||||
expect(record).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
module: 'crm',
|
||||
action: AUDIT_ACTION_LEAD_BATCH_EXPORT,
|
||||
operatorId: 7,
|
||||
operatorName: 'ExportAdmin',
|
||||
status: 1,
|
||||
})
|
||||
);
|
||||
const detailRaw = String(record.mock.calls[0][0].detail);
|
||||
const detail = JSON.parse(detailRaw);
|
||||
expect(detail.exportCount).toBe(2);
|
||||
expect(detailRaw).not.toContain('+971-50-1111111');
|
||||
expect(detailRaw).not.toContain('+971-50-2222222');
|
||||
expect(detail).not.toHaveProperty('phone');
|
||||
expect(detail).not.toHaveProperty('phones');
|
||||
});
|
||||
|
||||
it('导出查询失败时不得记成功审计', async () => {
|
||||
(leadService.crmLeadEntity.find as jest.Mock).mockRejectedValue(
|
||||
new Error('db down')
|
||||
);
|
||||
|
||||
await expect(leadService.batchExport({})).rejects.toThrow(/db down/);
|
||||
expect(record).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户