feat: refine report material workflow states
This commit is contained in:
@@ -103,8 +103,15 @@ export class AdminSmsConfigController {
|
||||
}
|
||||
|
||||
@Put('enterprise-signatures/:id')
|
||||
updateSignature(@Param('id') signatureId: string, @Body() body: UpdateSmsSignatureDto) {
|
||||
return this.smsConfig.updateSignature(signatureId, body);
|
||||
updateSignature(
|
||||
@Param('id') signatureId: string,
|
||||
@Body() body: UpdateSmsSignatureDto,
|
||||
@CurrentSessionUserId() reviewerId?: string,
|
||||
) {
|
||||
return this.smsConfig.updateSignature(signatureId, body, undefined, {
|
||||
initialAuditStatus: 'approved',
|
||||
reviewerId,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('drainage-infos')
|
||||
|
||||
@@ -440,15 +440,22 @@ export class SmsSignatureService {
|
||||
]
|
||||
: undefined,
|
||||
};
|
||||
const [items, total, pendingReportDetailTotal] = await Promise.all([
|
||||
const [items, total, pendingReportSummary] = await Promise.all([
|
||||
this.listSignatures({ ...query, page, pageSize }, true),
|
||||
this.prisma.smsSignature.count({ where }),
|
||||
this.countPendingReportDetails(where),
|
||||
this.countPendingReportSummary(where),
|
||||
]);
|
||||
return { items, total, page, pageSize, pendingReportDetailTotal };
|
||||
return {
|
||||
items,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
pendingReportMaterialTotal: pendingReportSummary.materialTotal,
|
||||
pendingReportDetailTotal: pendingReportSummary.detailTotal,
|
||||
};
|
||||
}
|
||||
|
||||
private async countPendingReportDetails(where: Prisma.SmsSignatureWhereInput) {
|
||||
private async countPendingReportSummary(where: Prisma.SmsSignatureWhereInput) {
|
||||
const signatures = await this.prisma.smsSignature.findMany({
|
||||
where: { AND: [where, { auditStatus: 'approved', pendingReport: true }] },
|
||||
select: {
|
||||
@@ -487,9 +494,11 @@ export class SmsSignatureService {
|
||||
},
|
||||
})
|
||||
: [];
|
||||
let total = 0;
|
||||
let materialTotal = 0;
|
||||
let detailTotal = 0;
|
||||
for (const signature of signatures) {
|
||||
if (!signature.applicationId || signature.application?.status !== 'active') continue;
|
||||
let hasPendingTarget = false;
|
||||
const generatedTargets = new Set<string>();
|
||||
for (const item of signature.reportBatchItems.filter(
|
||||
(entry) => entry.materialVersion === signature.materialVersion,
|
||||
@@ -526,11 +535,13 @@ export class SmsSignatureService {
|
||||
);
|
||||
if (task?.status === 'abandoned') continue;
|
||||
if (generatedTargets.has(`${channel.id}:${carrier}`) || generatedTargets.has(`${channel.id}:legacy`)) continue;
|
||||
total += 1;
|
||||
detailTotal += 1;
|
||||
hasPendingTarget = true;
|
||||
}
|
||||
}
|
||||
if (hasPendingTarget) materialTotal += 1;
|
||||
}
|
||||
return total;
|
||||
return { materialTotal, detailTotal };
|
||||
}
|
||||
|
||||
async getSignature(id: string) {
|
||||
@@ -817,7 +828,12 @@ export class SmsSignatureService {
|
||||
};
|
||||
}
|
||||
|
||||
async updateSignature(signatureId: string, data: UpdateSmsSignatureDto, tenantId?: string) {
|
||||
async updateSignature(
|
||||
signatureId: string,
|
||||
data: UpdateSmsSignatureDto,
|
||||
tenantId?: string,
|
||||
options: CreateSmsSignatureOptions = {},
|
||||
) {
|
||||
const signature = await this.prisma.smsSignature.findUnique({ where: { id: signatureId } });
|
||||
if (!signature || (tenantId && signature.tenantId !== tenantId)) {
|
||||
throw new NotFoundException('Signature not found');
|
||||
@@ -837,7 +853,9 @@ export class SmsSignatureService {
|
||||
(data.purpose !== undefined && data.purpose !== signature.purpose) ||
|
||||
(data.drainageInfo !== undefined &&
|
||||
JSON.stringify(drainageInfo ?? null) !== JSON.stringify(signature.drainageInfo ?? null));
|
||||
const auditStatus = materialChanged && signature.auditStatus === 'approved' ? 'pending' : data.auditStatus;
|
||||
const auditStatus =
|
||||
options.initialAuditStatus ??
|
||||
(materialChanged && signature.auditStatus === 'approved' ? 'pending' : data.auditStatus);
|
||||
const updated = await this.prisma.smsSignature.update({
|
||||
where: { id: signatureId },
|
||||
data: {
|
||||
@@ -845,7 +863,7 @@ export class SmsSignatureService {
|
||||
name,
|
||||
purpose: data.purpose,
|
||||
auditStatus,
|
||||
rejectReason: auditStatus === 'pending' ? null : undefined,
|
||||
rejectReason: auditStatus === 'pending' || auditStatus === 'approved' ? null : undefined,
|
||||
drainageInfo: drainageInfo as Prisma.InputJsonValue | undefined,
|
||||
materialVersion: materialChanged ? { increment: 1 } : undefined,
|
||||
pendingReport: materialChanged ? true : undefined,
|
||||
@@ -858,6 +876,21 @@ export class SmsSignatureService {
|
||||
updated.applicationId ?? undefined,
|
||||
drainageInfo,
|
||||
);
|
||||
if (
|
||||
options.initialAuditStatus === 'approved' &&
|
||||
(materialChanged || signature.auditStatus !== 'approved')
|
||||
) {
|
||||
await this.audit.createAuditRecord({
|
||||
tenantId: signature.tenantId,
|
||||
targetType: 'sms_signature',
|
||||
targetId: signatureId,
|
||||
action: 'admin_update_approved',
|
||||
statusBefore: signature.auditStatus,
|
||||
statusAfter: 'approved',
|
||||
reason: '运营端修改签名并自动审核通过',
|
||||
reviewerId: options.reviewerId,
|
||||
});
|
||||
}
|
||||
return {
|
||||
...updated,
|
||||
reportMaterialChanged: materialChanged,
|
||||
|
||||
@@ -47,6 +47,7 @@ export interface CreateSmsSignatureDto {
|
||||
|
||||
export interface CreateSmsSignatureOptions {
|
||||
initialAuditStatus?: string;
|
||||
reviewerId?: string;
|
||||
}
|
||||
|
||||
export type UpdateSmsSignatureDto = Partial<Omit<CreateSmsSignatureDto, 'tenantId'>> & {
|
||||
|
||||
@@ -1106,6 +1106,44 @@ describe('SmsConfigService', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it('keeps an operator-edited signature approved and records the operator audit', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.smsSignature.findUnique.mockResolvedValue({
|
||||
id: 'sig-1', tenantId: 'tenant-1', applicationId: 'app-1', name: '【旧签名】',
|
||||
purpose: '通知', drainageInfo: {}, auditStatus: 'approved',
|
||||
});
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
|
||||
const result = await service.updateSignature(
|
||||
'sig-1',
|
||||
{ name: '【新签名】' },
|
||||
undefined,
|
||||
{ initialAuditStatus: 'approved', reviewerId: 'operator-1' },
|
||||
);
|
||||
|
||||
expect(prisma.smsSignature.update).toHaveBeenCalledWith(expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
auditStatus: 'approved',
|
||||
rejectReason: null,
|
||||
materialVersion: { increment: 1 },
|
||||
pendingReport: true,
|
||||
}),
|
||||
}));
|
||||
expect(prisma.auditRecord.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({
|
||||
action: 'admin_update_approved',
|
||||
reviewerId: 'operator-1',
|
||||
statusBefore: 'approved',
|
||||
statusAfter: 'approved',
|
||||
}),
|
||||
});
|
||||
expect(result).toEqual(expect.objectContaining({
|
||||
auditStatus: 'approved',
|
||||
reportMaterialChanged: true,
|
||||
reportPoolAvailableAfter: 'immediate',
|
||||
}));
|
||||
});
|
||||
|
||||
it('resets an approved template to pending when key content is changed', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.smsTemplate.findUnique.mockResolvedValue({
|
||||
@@ -1223,6 +1261,7 @@ describe('SmsConfigService', () => {
|
||||
const result = await service.listSignaturesPage({ page: 1, pageSize: 10 });
|
||||
|
||||
expect(result.total).toBe(1);
|
||||
expect(result.pendingReportMaterialTotal).toBe(0);
|
||||
expect(result.pendingReportDetailTotal).toBe(0);
|
||||
expect(result.items[0]).toEqual(expect.objectContaining({ id: 'sig-1', name: '【签名A】' }));
|
||||
expect(result.items[0]).not.toHaveProperty('materials');
|
||||
@@ -1280,6 +1319,7 @@ describe('SmsConfigService', () => {
|
||||
|
||||
const result = await service.listSignaturesPage({ signatureKeyword: '测试', page: 1, pageSize: 10 });
|
||||
|
||||
expect(result.pendingReportMaterialTotal).toBe(1);
|
||||
expect(result.pendingReportDetailTotal).toBe(expectedTotal);
|
||||
expect(prisma.smsSignature.findMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -163,8 +163,13 @@ export class SmsConfigService implements OnModuleInit, OnModuleDestroy {
|
||||
return this.signatures.createSignature(data, options);
|
||||
}
|
||||
|
||||
async updateSignature(signatureId: string, data: UpdateSmsSignatureDto, tenantId?: string) {
|
||||
return this.signatures.updateSignature(signatureId, data, tenantId);
|
||||
async updateSignature(
|
||||
signatureId: string,
|
||||
data: UpdateSmsSignatureDto,
|
||||
tenantId?: string,
|
||||
options: CreateSmsSignatureOptions = {},
|
||||
) {
|
||||
return this.signatures.updateSignature(signatureId, data, tenantId, options);
|
||||
}
|
||||
|
||||
async updateClientSignature(signatureId: string, data: UpdateSmsSignatureDto, tenantId?: string) {
|
||||
|
||||
Reference in New Issue
Block a user