feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口

补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。

Co-authored-by: Cursor <cursoragent@cursor.com>
这个提交包含在:
wikiboss
2026-09-23 18:01:47 +08:00
父节点 8ea9c25386
当前提交 62e0ae4435
共有 227 个文件被更改,包括 21855 次插入 和 5264 次删除

查看文件

@@ -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();
});
});
});