feat: add reconciliation and quality reporting

This commit is contained in:
hectorzhao
2026-07-15 14:22:50 +08:00
parent 16311546af
commit 8c3336600e
32 changed files with 1730 additions and 200 deletions
@@ -52,6 +52,10 @@ function createPrismaMock() {
{ id: 'rule-2', applicationId: 'app-1', groupId: 'group-unicom', carrier: 'unicom', priority: 20, status: 'active' },
]),
},
commonReportField: {
findMany: jest.fn().mockResolvedValue([]),
count: jest.fn().mockResolvedValue(0),
},
smsSignature: {
findMany: jest.fn().mockResolvedValue([{
id: 'sig-1',
@@ -544,6 +548,59 @@ describe('SmsConfigService', () => {
]);
});
it('merges common report fields into every target channel requirement', async () => {
const prisma = createPrismaMock();
prisma.commonReportField.findMany.mockResolvedValue([{
id: 'common-1', reportType: 'signature', required: true, status: 'active',
drainageField: { id: 'field-common', code: 'creditCode', name: '统一社会信用代码', fieldType: 'string', description: null, status: 'active' },
}]);
prisma.channelRouteRule.findMany.mockResolvedValue([{
id: 'route-1', priority: 10,
group: {
id: 'group-1', name: '默认通道组',
items: [{ channel: { id: 'channel-1', code: 'CH-1', name: '通道一', reportFields: [] } }],
},
}] as never);
const service = new SmsConfigService(prisma as never);
await expect(service.getApplicationReportFields('app-1', 'signature')).resolves.toEqual([
expect.objectContaining({
id: 'field-common',
required: true,
reportTypes: ['signature'],
commonReportTypes: ['signature'],
channels: [expect.objectContaining({ id: 'channel-1', source: 'common', required: true })],
}),
]);
});
it('requires common signature fields even when a signature is not bound to an application', async () => {
const prisma = createPrismaMock();
prisma.commonReportField.findMany.mockResolvedValue([{
id: 'common-1', reportType: 'signature', required: true, status: 'active',
drainageField: { id: 'field-common', code: 'creditCode', name: '统一社会信用代码', fieldType: 'string', description: null, status: 'active' },
}]);
const service = new SmsConfigService(prisma as never);
await expect(service.createSignature({ tenantId: 'tenant-1', name: '无应用签名' }))
.rejects.toThrow('缺少必填签名报备资料:统一社会信用代码');
expect(prisma.smsSignature.create).not.toHaveBeenCalled();
});
it('requires common drainage fields when adding drainage info without an application', async () => {
const prisma = createPrismaMock();
prisma.smsSignature.findUnique.mockResolvedValue({ id: 'sig-1', tenantId: 'tenant-1', applicationId: null, auditStatus: 'approved' });
prisma.commonReportField.findMany.mockResolvedValue([{
id: 'common-2', reportType: 'drainage', required: true, status: 'active',
drainageField: { id: 'field-site', code: 'siteOwner', name: '网站主体', fieldType: 'string', description: null, status: 'active' },
}]);
const service = new SmsConfigService(prisma as never);
await expect(service.createDrainageInfo('sig-1', { siteName: '官网', url: 'https://example.com', reportValues: {} }, {}, 'tenant-1'))
.rejects.toThrow('引流信息缺少必填报备资料:网站主体');
expect(prisma.smsDrainageInfo.create).not.toHaveBeenCalled();
});
it('validates and persists dynamic signature report values by channel without bypassing drainage audit', async () => {
const prisma = createPrismaMock();
prisma.smsSignature.findUnique.mockResolvedValue({ id: 'sig-1', tenantId: 'tenant-1', applicationId: 'app-1', auditStatus: 'draft' });