feat: complete reporting and filing workflows

This commit is contained in:
hectorzhao
2026-07-28 20:28:47 +08:00
parent 352a6293b4
commit 99c8c7c68b
52 changed files with 3490 additions and 376 deletions
@@ -34,7 +34,9 @@ function createPrismaMock() {
delete: jest.fn().mockResolvedValue({ id: 'field-1' }),
},
channelReportField: {
findMany: jest.fn().mockResolvedValue([]),
count: jest.fn().mockResolvedValue(0),
deleteMany: jest.fn(),
},
commonReportField: {
findMany: jest.fn().mockResolvedValue([]),
@@ -49,6 +51,7 @@ function createPrismaMock() {
operationLog: {
create: jest.fn(),
},
$transaction: jest.fn(),
};
}
@@ -64,13 +67,45 @@ describe('DictionariesService', () => {
it('returns drainage field usage counts and blocks deleting fields used by channels', async () => {
const prisma = createPrismaMock();
prisma.drainageField.findMany.mockResolvedValue([{ id: 'field-1', code: 'license', _count: { channelReportFields: 2, commonReportFields: 0 } }]);
prisma.drainageField.findMany.mockResolvedValue([{ id: 'field-1', code: 'license' }]);
prisma.channelReportField.findMany.mockResolvedValue([
{ drainageFieldId: 'field-1', channelId: 'channel-1' },
{ drainageFieldId: 'field-1', channelId: 'channel-1' },
{ drainageFieldId: 'field-1', channelId: 'channel-2' },
]);
prisma.channelReportField.count.mockResolvedValue(2);
const service = new DictionariesService(prisma as never);
await expect(service.listDrainageFields()).resolves.toEqual([{ id: 'field-1', code: 'license', usageCount: 2, commonUsageCount: 0 }]);
await expect(service.deleteDrainageField('field-1')).rejects.toThrow('不能删除');
expect(prisma.drainageField.delete).not.toHaveBeenCalled();
expect(prisma.channelReportField.findMany).toHaveBeenCalledWith({
where: {
drainageFieldId: { in: ['field-1'] },
channel: { status: { not: 'deleted' } },
},
select: { drainageFieldId: true, channelId: true },
});
});
it('ignores deleted-channel references and removes those stale mappings when deleting the field', async () => {
const prisma = createPrismaMock();
const tx = {
channelReportField: { deleteMany: jest.fn().mockResolvedValue({ count: 1 }) },
drainageField: { delete: jest.fn().mockResolvedValue({ id: 'field-1' }) },
};
prisma.$transaction.mockImplementation((callback) => callback(tx));
const service = new DictionariesService(prisma as never);
await expect(service.deleteDrainageField('field-1')).resolves.toEqual({ id: 'field-1' });
expect(prisma.channelReportField.count).toHaveBeenCalledWith({
where: { drainageFieldId: 'field-1', channel: { status: { not: 'deleted' } } },
});
expect(tx.channelReportField.deleteMany).toHaveBeenCalledWith({
where: { drainageFieldId: 'field-1', channel: { status: 'deleted' } },
});
expect(tx.drainageField.delete).toHaveBeenCalledWith({ where: { id: 'field-1' } });
});
it('creates and deletes real common signature and drainage field configurations', async () => {
+37 -6
View File
@@ -294,13 +294,37 @@ export class DictionariesService {
async listDrainageFields() {
const fields = await this.prisma.drainageField.findMany({
include: { _count: { select: { channelReportFields: true, commonReportFields: true } } },
orderBy: { createdAt: 'desc' },
});
return fields.map(({ _count, ...field }) => ({
const fieldIds = fields.map((field) => field.id);
const [channelReferences, commonReferences] = fieldIds.length ? await Promise.all([
this.prisma.channelReportField.findMany({
where: {
drainageFieldId: { in: fieldIds },
channel: { status: { not: 'deleted' } },
},
select: { drainageFieldId: true, channelId: true },
}),
this.prisma.commonReportField.findMany({
where: { drainageFieldId: { in: fieldIds } },
select: { drainageFieldId: true },
}),
]) : [[], []];
const channelsByField = new Map<string, Set<string>>();
for (const reference of channelReferences) {
if (!reference.drainageFieldId) continue;
const channelIds = channelsByField.get(reference.drainageFieldId) ?? new Set<string>();
channelIds.add(reference.channelId);
channelsByField.set(reference.drainageFieldId, channelIds);
}
const commonCountByField = new Map<string, number>();
for (const reference of commonReferences) {
commonCountByField.set(reference.drainageFieldId, (commonCountByField.get(reference.drainageFieldId) ?? 0) + 1);
}
return fields.map((field) => ({
...field,
usageCount: _count.channelReportFields,
commonUsageCount: _count.commonReportFields,
usageCount: channelsByField.get(field.id)?.size ?? 0,
commonUsageCount: commonCountByField.get(field.id) ?? 0,
}));
}
@@ -326,13 +350,20 @@ export class DictionariesService {
async deleteDrainageField(id: string) {
const [usageCount, commonUsageCount] = await Promise.all([
this.prisma.channelReportField.count({ where: { drainageFieldId: id } }),
this.prisma.channelReportField.count({
where: { drainageFieldId: id, channel: { status: { not: 'deleted' } } },
}),
this.prisma.commonReportField.count({ where: { drainageFieldId: id } }),
]);
if (usageCount > 0 || commonUsageCount > 0) {
throw new BadRequestException(`该字段已被 ${usageCount} 个通道和 ${commonUsageCount} 个通用配置使用,不能删除`);
}
return this.prisma.drainageField.delete({ where: { id } });
return this.prisma.$transaction(async (tx) => {
await tx.channelReportField.deleteMany({
where: { drainageFieldId: id, channel: { status: 'deleted' } },
});
return tx.drainageField.delete({ where: { id } });
});
}
listCommonReportFields() {