feat: refine template deletion and channel group filters
This commit is contained in:
@@ -80,7 +80,6 @@ describe('DeletionGovernanceService', () => {
|
||||
prisma.smsTemplate.findFirst.mockResolvedValue({
|
||||
id: 'template-1', name: '验证码模板', auditStatus: 'approved', updatedAt: now,
|
||||
tenant: { name: '示例企业' }, application: { name: '验证码应用' }, signature: { name: '示例签名' },
|
||||
sendTasks: [], batchTasks: [],
|
||||
});
|
||||
|
||||
const result = await service.preflight('template', 'template-1', 'tenant-1');
|
||||
@@ -90,11 +89,9 @@ describe('DeletionGovernanceService', () => {
|
||||
expect(result.identity.tenant).toBe('示例企业');
|
||||
expect(result.requiredSelections).toEqual([]);
|
||||
expect(prisma.smsTemplate.findFirst).toHaveBeenCalledWith(expect.objectContaining({
|
||||
include: expect.objectContaining({
|
||||
sendTasks: expect.objectContaining({ where: { status: { notIn: ['approved', 'rejected'] } } }),
|
||||
batchTasks: expect.objectContaining({ where: { status: { notIn: expect.arrayContaining(['finished', 'canceled']) } } }),
|
||||
}),
|
||||
include: expect.not.objectContaining({ sendTasks: expect.anything(), batchTasks: expect.anything() }),
|
||||
}));
|
||||
expect(result.impacts).toContain('已创建任务继续使用保存的内容快照');
|
||||
});
|
||||
|
||||
it('turns signature dependencies into mandatory cascade selections', async () => {
|
||||
@@ -171,11 +168,10 @@ describe('DeletionGovernanceService', () => {
|
||||
prisma.smsTemplate.findFirst.mockResolvedValue({
|
||||
id: 'template-1', name: '验证码模板', auditStatus: 'approved', updatedAt: now,
|
||||
tenant: { name: '示例企业' }, application: { name: '验证码应用' }, signature: null,
|
||||
sendTasks: [], batchTasks: [],
|
||||
});
|
||||
tx.operationLog.findFirst.mockResolvedValue(null);
|
||||
tx.smsTemplate.findFirst.mockResolvedValue({
|
||||
id: 'template-1', tenantId: 'tenant-1', sendTasks: [], batchTasks: [],
|
||||
id: 'template-1', tenantId: 'tenant-1',
|
||||
});
|
||||
tx.smsTemplate.updateMany.mockResolvedValue({ count: 1 });
|
||||
tx.operationLog.create.mockResolvedValue({ id: 'operation-1' });
|
||||
@@ -185,6 +181,10 @@ describe('DeletionGovernanceService', () => {
|
||||
}, 'tenant-1');
|
||||
|
||||
expect(result).toEqual({ operationId: 'operation-1', status: 'deleted', replayed: false });
|
||||
expect(tx.smsTemplate.findFirst).toHaveBeenCalledWith({
|
||||
where: { id: 'template-1', tenantId: 'tenant-1' },
|
||||
select: { id: true, tenantId: true },
|
||||
});
|
||||
expect(tx.smsTemplate.updateMany).toHaveBeenCalledWith(expect.objectContaining({ data: { auditStatus: 'deleted' } }));
|
||||
expect(tx.operationLog.create).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ action: 'governance.delete', userId: 'user-1' }) }));
|
||||
});
|
||||
@@ -241,7 +241,6 @@ describe('DeletionGovernanceService', () => {
|
||||
prisma.smsTemplate.findFirst.mockResolvedValue({
|
||||
id: 'template-1', name: '验证码模板', auditStatus: 'approved', updatedAt: now,
|
||||
tenant: { name: '示例企业' }, application: { name: '验证码应用' }, signature: null,
|
||||
sendTasks: [], batchTasks: [],
|
||||
});
|
||||
await expect(service.delete('template', 'template-1', {
|
||||
expectedUpdatedAt: '2026-07-20T10:00:00.000Z', idempotencyKey: 'stale', reason: '测试版本冲突',
|
||||
|
||||
@@ -203,19 +203,13 @@ export class DeletionGovernanceService {
|
||||
where: { id, ...(tenantId ? { tenantId } : {}) },
|
||||
include: {
|
||||
tenant: { select: { name: true } }, application: { select: { name: true } }, signature: { select: { name: true } },
|
||||
sendTasks: { where: { status: { notIn: TERMINAL_SEND_TASK_STATUSES } }, select: { id: true, status: true } },
|
||||
batchTasks: { where: { status: { notIn: TERMINAL_BATCH_TASK_STATUSES } }, select: { id: true, status: true } },
|
||||
},
|
||||
});
|
||||
if (!item) throw new NotFoundException('模板不存在或无权访问');
|
||||
const dependencies: Dependency[] = [
|
||||
dep('send_tasks', '未结束发送任务', item.sendTasks.map((row) => `${row.id}(${row.status})`)),
|
||||
dep('batch_tasks', '未结束批量任务', item.batchTasks.map((row) => `${row.id}(${row.status})`)),
|
||||
];
|
||||
return buildPreflight('template', item.id, item.updatedAt, {
|
||||
name: item.name, id: item.id, tenant: item.tenant.name, application: item.application.name,
|
||||
signature: item.signature?.name ?? '未绑定',
|
||||
}, item.auditStatus, dependencies, ['删除后不能用于新发送任务', '历史消息、计费和审核记录继续保留']);
|
||||
}, item.auditStatus, [], ['删除后不能用于新发送任务', '已创建任务继续使用保存的内容快照', '历史消息、计费和审核记录继续保留']);
|
||||
}
|
||||
|
||||
private async prepareChannelDeletion(tx: Prisma.TransactionClient, id: string, body: DeleteTargetDto, reason?: string) {
|
||||
@@ -305,17 +299,9 @@ export class DeletionGovernanceService {
|
||||
private async prepareTemplateDeletion(tx: Prisma.TransactionClient, id: string, tenantId?: string) {
|
||||
const item = await tx.smsTemplate.findFirst({
|
||||
where: { id, ...(tenantId ? { tenantId } : {}) },
|
||||
include: {
|
||||
sendTasks: { where: { status: { notIn: TERMINAL_SEND_TASK_STATUSES } }, select: { id: true } },
|
||||
batchTasks: { where: { status: { notIn: TERMINAL_BATCH_TASK_STATUSES } }, select: { id: true } },
|
||||
},
|
||||
select: { id: true, tenantId: true },
|
||||
});
|
||||
if (!item) throw new NotFoundException('模板不存在或无权访问');
|
||||
const blockers = [
|
||||
item.sendTasks.length ? `未结束发送任务共 ${item.sendTasks.length} 项,请先解除或完成` : '',
|
||||
item.batchTasks.length ? `未结束批量任务共 ${item.batchTasks.length} 项,请先解除或完成` : '',
|
||||
].filter(Boolean);
|
||||
if (blockers.length) throw new ConflictException({ message: '当前对象不允许删除', blockedReasons: blockers });
|
||||
return { tenantId: item.tenantId, affectedSignatureIds: [] as string[], detail: {} };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user