Files
lislgosms/api/src/sms-config/audit.service.ts
T

196 lines
7.0 KiB
TypeScript

import { BadRequestException, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import type { ReviewDto, StatusChangeDto } from './sms-config.contracts';
import { SmsApplicationLifecycleService } from './application-lifecycle.service';
import { SmsReportValidationService } from './report-validation.service';
import { writeUniqueSignature } from './signature-uniqueness';
/** R3 domain service. Kept framework-agnostic and composed behind SmsConfigService. */
export class SmsAuditService {
constructor(
private readonly prisma: PrismaService,
private readonly lifecycle: SmsApplicationLifecycleService,
private readonly reportValidation: SmsReportValidationService,
) {}
listAuditRecords(targetType?: string, targetId?: string) {
return this.prisma.auditRecord.findMany({
where: {
targetType,
targetId,
},
include: {
reviewer: { select: { id: true, username: true, displayName: true } },
},
orderBy: { createdAt: 'desc' },
});
}
approveSignature(signatureId: string, data: ReviewDto) {
return this.reviewSignature(signatureId, 'approved', 'approve', data);
}
rejectSignature(signatureId: string, data: ReviewDto) {
return this.reviewSignature(signatureId, 'rejected', 'reject', data);
}
approveTemplate(templateId: string, data: ReviewDto) {
return this.reviewTemplate(templateId, 'approved', 'approve', data);
}
rejectTemplate(templateId: string, data: ReviewDto) {
return this.reviewTemplate(templateId, 'rejected', 'reject', data);
}
async changeSignatureStatus(signatureId: string, data: StatusChangeDto, tenantId?: string) {
const signature = await this.prisma.smsSignature.findUnique({ where: { id: signatureId } });
if (!signature || (tenantId && signature.tenantId !== tenantId)) {
throw new NotFoundException('Signature not found');
}
const status = data.status ?? 'deleted';
const updated = await writeUniqueSignature(this.prisma, { ...signature, auditStatus: status }, () =>
this.prisma.smsSignature.update({ where: { id: signatureId }, data: { auditStatus: status } }),
);
await this.lifecycle.writeOperationLog(
signature.tenantId,
data.operatorId,
`sms_signature.${status}`,
'sms_signature',
signatureId,
{
statusBefore: signature.auditStatus,
statusAfter: status,
reason: data.reason,
},
);
return updated;
}
async changeTemplateStatus(templateId: string, data: StatusChangeDto, tenantId?: string) {
const template = await this.prisma.smsTemplate.findUnique({ where: { id: templateId } });
if (!template || (tenantId && template.tenantId !== tenantId)) {
throw new NotFoundException('Template not found');
}
const status = data.status ?? 'deleted';
const updated = await this.prisma.smsTemplate.update({ where: { id: templateId }, data: { auditStatus: status } });
await this.lifecycle.writeOperationLog(
template.tenantId,
data.operatorId,
`sms_template.${status}`,
'sms_template',
templateId,
{
statusBefore: template.auditStatus,
statusAfter: status,
reason: data.reason,
},
);
return updated;
}
async reviewSignature(signatureId: string, statusAfter: string, action: string, data: ReviewDto) {
const signature = await this.prisma.smsSignature.findUnique({ where: { id: signatureId } });
if (!signature) {
throw new NotFoundException('Signature not found');
}
const reviewerId = await this.resolveReviewerId(data.reviewerId);
const updated = await writeUniqueSignature(this.prisma, { ...signature, auditStatus: statusAfter }, () =>
this.prisma.smsSignature.update({
where: { id: signatureId },
data: {
auditStatus: statusAfter,
rejectReason: statusAfter === 'rejected' ? data.reason : null,
},
}),
);
await this.createAuditRecord({
tenantId: signature.tenantId,
targetType: 'sms_signature',
targetId: signatureId,
action,
statusBefore: signature.auditStatus,
statusAfter,
reason: data.reason,
reviewerId,
});
return updated;
}
async reviewDrainageInfo(itemId: string, statusAfter: string, action: string, data: ReviewDto) {
const item = await this.prisma.smsDrainageInfo.findUnique({ where: { id: itemId } });
if (!item) throw new NotFoundException('Drainage info not found');
if (!['pending', 'rejected'].includes(item.auditStatus)) {
throw new BadRequestException('只有待审核或已驳回的引流信息可以审核');
}
if (statusAfter === 'rejected' && !data.reason?.trim()) {
throw new BadRequestException('驳回引流信息时必须填写原因');
}
const reviewerId = await this.resolveReviewerId(data.reviewerId);
const updated = await this.prisma.smsDrainageInfo.update({
where: { id: itemId },
data: {
auditStatus: statusAfter,
rejectReason: statusAfter === 'rejected' ? data.reason?.trim() : null,
reviewedAt: new Date(),
},
include: { tenant: true, signature: true, application: true },
});
await this.createAuditRecord({
tenantId: item.tenantId,
targetType: 'sms_drainage_info',
targetId: itemId,
action,
statusBefore: item.auditStatus,
statusAfter,
reason: data.reason,
reviewerId,
});
if (statusAfter === 'approved') await this.reportValidation.activateDrainageReporting(itemId);
else await this.reportValidation.suspendDrainageReporting(itemId, data.reason?.trim() || '引流信息运营审核驳回');
return updated;
}
async reviewTemplate(templateId: string, statusAfter: string, action: string, data: ReviewDto) {
const template = await this.prisma.smsTemplate.findUnique({ where: { id: templateId } });
if (!template) {
throw new NotFoundException('Template not found');
}
const reviewerId = await this.resolveReviewerId(data.reviewerId);
const updated = await this.prisma.smsTemplate.update({
where: { id: templateId },
data: {
auditStatus: statusAfter,
rejectReason: statusAfter === 'rejected' ? data.reason : null,
},
});
await this.createAuditRecord({
tenantId: template.tenantId,
targetType: 'sms_template',
targetId: templateId,
action,
statusBefore: template.auditStatus,
statusAfter,
reason: data.reason,
reviewerId,
});
return updated;
}
async resolveReviewerId(reviewerId?: string) {
if (!reviewerId) {
return undefined;
}
const reviewer = await this.prisma.user.findUnique({ where: { id: reviewerId }, select: { id: true } });
if (!reviewer) {
throw new BadRequestException('reviewerId does not reference an existing user');
}
return reviewerId;
}
createAuditRecord(data: Prisma.AuditRecordUncheckedCreateInput) {
return this.prisma.auditRecord.create({ data });
}
}