196 lines
7.0 KiB
TypeScript
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 });
|
|
}
|
|
}
|