feat: enhance operations dashboard and reporting controls

This commit is contained in:
hectorzhao
2026-09-04 16:26:22 +08:00
parent bc18c7ff12
commit 48d0363920
23 changed files with 544 additions and 49 deletions
@@ -33,6 +33,7 @@ function createPrismaMock() {
findMany: jest.fn().mockResolvedValue([]),
findUnique: jest.fn().mockResolvedValue(null),
create: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'field-1', ...data })),
update: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'field-1', ...data })),
delete: jest.fn().mockResolvedValue({ id: 'field-1' }),
},
channelReportField: {
@@ -46,6 +47,7 @@ function createPrismaMock() {
count: jest.fn().mockResolvedValue(0),
create: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'common-1', ...data })),
delete: jest.fn().mockResolvedValue({ id: 'common-1' }),
update: jest.fn(),
},
smsApplication: {
findUnique: jest.fn().mockResolvedValue({ id: 'app-1', tenantId: 'tenant-1' }),
@@ -76,6 +78,42 @@ describe('DictionariesService', () => {
await expect(service.updateCommonReportField('common-1', body)).rejects.toThrow('已停用');
await expect(service.updateCommonReportField('common-1', { ...body, required: 'false' as never })).rejects.toThrow('无效');
});
it('reorders every common field in one report type transaction and writes an audit trail', async () => {
const prisma = createPrismaMock();
const tx = {
commonReportField: {
update: jest.fn().mockResolvedValue({}),
findMany: jest.fn().mockResolvedValue([{ id: 'common-2' }, { id: 'common-1' }]),
},
operationLog: { create: jest.fn().mockResolvedValue({}) },
};
prisma.$transaction.mockImplementation((callback) => callback(tx));
const service = new DictionariesService(prisma as never);
await expect(
service.reorderCommonReportFields({ reportType: 'signature', ids: ['common-2', 'common-1'] }, 'admin-1'),
).resolves.toEqual([{ id: 'common-2' }, { id: 'common-1' }]);
expect(tx.commonReportField.update).toHaveBeenNthCalledWith(1, {
where: { id: 'common-2' },
data: { sortOrder: 10 },
});
expect(tx.commonReportField.update).toHaveBeenNthCalledWith(2, {
where: { id: 'common-1' },
data: { sortOrder: 20 },
});
expect(tx.operationLog.create).toHaveBeenCalledWith({
data: expect.objectContaining({ action: 'common_report_field.reorder', userId: 'admin-1' }),
});
expect(prisma.$transaction).toHaveBeenCalledWith(expect.any(Function), { isolationLevel: 'Serializable' });
tx.commonReportField.findMany.mockResolvedValue([{ id: 'common-1' }]);
await expect(
service.reorderCommonReportFields({ reportType: 'signature', ids: ['common-1'] }),
).resolves.toEqual([{ id: 'common-1' }]);
await expect(
service.reorderCommonReportFields({ reportType: 'signature', ids: ['common-1', 'common-2'] }),
).rejects.toThrow('排序范围已变化');
});
it('builds the enterprise province and city library from distinct real phone segment regions', async () => {
const prisma = createPrismaMock();
prisma.phoneSegment.findMany.mockResolvedValue([
@@ -130,6 +168,32 @@ describe('DictionariesService', () => {
});
});
it('edits an unreferenced field atomically and protects mapping keys once referenced', async () => {
const prisma = createPrismaMock();
const existing = { id: 'field-1', code: 'license', name: '主体证明', fieldType: 'file', description: null };
prisma.drainageField.findUnique.mockResolvedValue(existing as never);
const tx = {
drainageField: { update: jest.fn().mockResolvedValue({ ...existing, name: '企业主体证明', description: '最新版' }) },
operationLog: { create: jest.fn().mockResolvedValue({}) },
};
prisma.$transaction.mockImplementation((callback) => callback(tx));
const service = new DictionariesService(prisma as never);
await expect(service.updateDrainageField('field-1', {
code: 'license', name: '企业主体证明', fieldType: 'file', description: ' 最新版 ',
}, 'admin-1')).resolves.toEqual(expect.objectContaining({ name: '企业主体证明' }));
expect(tx.drainageField.update).toHaveBeenCalledWith({
where: { id: 'field-1' },
data: { code: 'license', name: '企业主体证明', fieldType: 'file', description: '最新版' },
});
expect(tx.operationLog.create).toHaveBeenCalledWith({ data: expect.objectContaining({ action: 'drainage_field.update', userId: 'admin-1' }) });
prisma.channelReportField.count.mockResolvedValue(1);
await expect(service.updateDrainageField('field-1', {
code: 'newCode', name: '企业主体证明', fieldType: 'file', description: '',
})).rejects.toThrow('不能修改字段代码或类型');
});
it('ignores deleted-channel references and removes those stale mappings when deleting the field', async () => {
const prisma = createPrismaMock();
const tx = {