feat: complete reporting and filing workflows
This commit is contained in:
@@ -151,6 +151,120 @@ describe('ReportMaterialsService', () => {
|
||||
expect(prisma.reportMaterialBatch.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('stages imported signatures for review without changing or approving business data', async () => {
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const sheet = workbook.addWorksheet('签名资料');
|
||||
sheet.addRow(['短信签名', '用途说明']);
|
||||
sheet.addRow(['待审签名', '验证码']);
|
||||
const content = Buffer.from(await workbook.xlsx.writeBuffer());
|
||||
const stagedRows: Array<Record<string, unknown>> = [];
|
||||
const prisma = {
|
||||
reportMaterialImportBatch: {
|
||||
findUnique: jest.fn().mockResolvedValue({
|
||||
id: 'import-review-1',
|
||||
tenantId: 'tenant-1',
|
||||
applicationId: 'app-1',
|
||||
fileObjectId: 'source-1',
|
||||
fileName: '签名资料.xlsx',
|
||||
reportType: 'signature',
|
||||
status: 'analyzed',
|
||||
sheetName: '签名资料',
|
||||
dataStartRow: 2,
|
||||
}),
|
||||
update: jest.fn().mockImplementation(({ data }: { data: Record<string, unknown> }) => Promise.resolve({ id: 'import-review-1', ...data, items: stagedRows })),
|
||||
},
|
||||
reportMaterialImportItem: {
|
||||
createMany: jest.fn().mockImplementation(({ data }: { data: Array<Record<string, unknown>> }) => {
|
||||
stagedRows.push(...data);
|
||||
return Promise.resolve({ count: data.length });
|
||||
}),
|
||||
},
|
||||
smsSignature: { findFirst: jest.fn().mockResolvedValue(null) },
|
||||
operationLog: { create: jest.fn().mockResolvedValue({ id: 'log-import-review' }) },
|
||||
$transaction: jest.fn().mockImplementation((callback: (tx: unknown) => unknown) => callback(prisma)),
|
||||
};
|
||||
const files = { getDownload: jest.fn().mockResolvedValue({ content }) };
|
||||
const smsConfig = {
|
||||
createSignature: jest.fn(),
|
||||
updateSignature: jest.fn(),
|
||||
approveSignature: jest.fn(),
|
||||
};
|
||||
const service = new ReportMaterialsService(prisma as never, files as never, smsConfig as never);
|
||||
|
||||
const result = await service.commitImport('import-review-1', {
|
||||
operatorId: 'operator-1',
|
||||
mappings: [
|
||||
{ sourceHeader: '短信签名', sourceColumnIndex: 1, targetFieldCode: 'signature_name', targetKind: 'signatureName', fieldType: 'string', required: true },
|
||||
{ sourceHeader: '用途说明', sourceColumnIndex: 2, targetFieldCode: 'purpose', targetKind: 'purpose', fieldType: 'string' },
|
||||
],
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({ status: 'pending_review', successCount: 1, failedCount: 0 });
|
||||
expect(stagedRows).toEqual([expect.objectContaining({
|
||||
rowNumber: 2,
|
||||
reportType: 'signature',
|
||||
operation: 'create',
|
||||
status: 'pending_review',
|
||||
payload: expect.objectContaining({ name: '待审签名', purpose: '验证码' }),
|
||||
})]);
|
||||
expect(smsConfig.createSignature).not.toHaveBeenCalled();
|
||||
expect(smsConfig.updateSignature).not.toHaveBeenCalled();
|
||||
expect(smsConfig.approveSignature).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('allows rejecting selected imported rows without requiring a reason', async () => {
|
||||
const prisma = {
|
||||
reportMaterialImportBatch: {
|
||||
findUnique: jest.fn().mockResolvedValue({
|
||||
id: 'import-review-2',
|
||||
items: [{ id: 'item-1', rowNumber: 2, status: 'pending_review' }],
|
||||
}),
|
||||
update: jest.fn().mockResolvedValue({}),
|
||||
},
|
||||
reportMaterialImportItem: {
|
||||
update: jest.fn().mockResolvedValue({}),
|
||||
groupBy: jest.fn().mockResolvedValue([{ status: 'rejected', _count: { _all: 1 } }]),
|
||||
},
|
||||
};
|
||||
const service = new ReportMaterialsService(prisma as never, {} as never, {} as never);
|
||||
|
||||
await expect(service.reviewImportItems('import-review-2', {
|
||||
decision: 'reject',
|
||||
itemIds: ['item-1'],
|
||||
reviewerId: 'reviewer-1',
|
||||
})).resolves.toMatchObject({ status: 'rejected', rejectedCount: 1, failedCount: 0 });
|
||||
expect(prisma.reportMaterialImportItem.update).toHaveBeenCalledWith({
|
||||
where: { id: 'item-1' },
|
||||
data: expect.objectContaining({ status: 'rejected', reviewReason: undefined, reviewedById: 'reviewer-1' }),
|
||||
});
|
||||
});
|
||||
|
||||
it('calculates generated batch totals and success rate from per-channel report tasks', async () => {
|
||||
const prisma = {
|
||||
reportMaterialBatch: {
|
||||
findMany: jest.fn().mockResolvedValue([{
|
||||
id: 'batch-stats-1',
|
||||
batchNo: 'RB-STATS-1',
|
||||
exportFiles: [
|
||||
{ items: [{ task: { id: 'task-1', status: 'approved' } }, { task: { id: 'task-2', status: 'rejected' } }] },
|
||||
{ items: [{ task: { id: 'task-3', status: 'approved' } }] },
|
||||
],
|
||||
items: [],
|
||||
}]),
|
||||
count: jest.fn().mockResolvedValue(1),
|
||||
},
|
||||
};
|
||||
const service = new ReportMaterialsService(prisma as never, {} as never, {} as never);
|
||||
|
||||
await expect(service.listBatches({ keyword: 'RB-STATS', page: 2, pageSize: 10 })).resolves.toMatchObject({
|
||||
items: [{ id: 'batch-stats-1', reportTotal: 3, successCount: 2, successRate: 2 / 3 }],
|
||||
total: 1,
|
||||
page: 2,
|
||||
pageSize: 10,
|
||||
});
|
||||
expect(prisma.reportMaterialBatch.findMany).toHaveBeenCalledWith(expect.objectContaining({ skip: 10, take: 10 }));
|
||||
});
|
||||
|
||||
it('rejects malformed preflight items as a readable 400 before Prisma is called', async () => {
|
||||
const prisma = { smsSignature: { findUnique: jest.fn() } };
|
||||
const service = new ReportMaterialsService(prisma as never, {} as never, {} as never);
|
||||
|
||||
Reference in New Issue
Block a user