feat: complete signature and drainage reporting workflows
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE "ChannelSignatureReportTask"
|
||||
ADD COLUMN "reportType" TEXT NOT NULL DEFAULT 'signature',
|
||||
ADD COLUMN "drainageItemId" TEXT;
|
||||
|
||||
CREATE INDEX "ChannelSignatureReportTask_signatureId_drainageItemId_channelId_idx"
|
||||
ON "ChannelSignatureReportTask"("signatureId", "drainageItemId", "channelId");
|
||||
|
||||
CREATE INDEX "ChannelSignatureReportTask_reportType_status_idx"
|
||||
ON "ChannelSignatureReportTask"("reportType", "status");
|
||||
@@ -0,0 +1,45 @@
|
||||
INSERT INTO "ChannelSignatureReportTask" (
|
||||
"id", "tenantId", "signatureId", "channelId", "reportType", "drainageItemId", "status", "createdAt", "updatedAt"
|
||||
)
|
||||
SELECT
|
||||
'drainage_' || md5(random()::text || clock_timestamp()::text || material."id"),
|
||||
signature."tenantId",
|
||||
material."signatureId",
|
||||
material."channelId",
|
||||
'drainage',
|
||||
material."drainageItemId",
|
||||
'pending',
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM (
|
||||
SELECT DISTINCT ON ("signatureId", "drainageItemId", "channelId")
|
||||
"id", "signatureId", "drainageItemId", "channelId"
|
||||
FROM "DrainageReportMaterial"
|
||||
ORDER BY "signatureId", "drainageItemId", "channelId", "createdAt"
|
||||
) material
|
||||
JOIN "SmsSignature" signature ON signature."id" = material."signatureId"
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "ChannelSignatureReportTask" task
|
||||
WHERE task."signatureId" = material."signatureId"
|
||||
AND task."channelId" = material."channelId"
|
||||
AND task."reportType" = 'drainage'
|
||||
AND task."drainageItemId" = material."drainageItemId"
|
||||
);
|
||||
|
||||
INSERT INTO "ChannelSignatureReportRecord" (
|
||||
"id", "taskId", "channelId", "action", "statusAfter", "reason", "createdAt"
|
||||
)
|
||||
SELECT
|
||||
'drainage_record_' || md5(random()::text || clock_timestamp()::text || task."id"),
|
||||
task."id",
|
||||
task."channelId",
|
||||
'migration_backfill',
|
||||
task."status",
|
||||
'从已有引流报备材料回填任务',
|
||||
CURRENT_TIMESTAMP
|
||||
FROM "ChannelSignatureReportTask" task
|
||||
WHERE task."reportType" = 'drainage'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM "ChannelSignatureReportRecord" record WHERE record."taskId" = task."id"
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
CREATE UNIQUE INDEX "ChannelSignatureReportTask_target_key"
|
||||
ON "ChannelSignatureReportTask"(
|
||||
"signatureId",
|
||||
"channelId",
|
||||
"reportType",
|
||||
COALESCE("drainageItemId", '')
|
||||
);
|
||||
@@ -720,6 +720,8 @@ model ChannelSignatureReportTask {
|
||||
tenantId String
|
||||
signatureId String
|
||||
channelId String
|
||||
reportType String @default("signature")
|
||||
drainageItemId String?
|
||||
status String @default("pending")
|
||||
reason String?
|
||||
createdById String?
|
||||
@@ -734,6 +736,8 @@ model ChannelSignatureReportTask {
|
||||
|
||||
@@index([tenantId, status])
|
||||
@@index([signatureId, channelId])
|
||||
@@index([signatureId, drainageItemId, channelId])
|
||||
@@index([reportType, status])
|
||||
}
|
||||
|
||||
model ChannelSignatureReportRecord {
|
||||
|
||||
@@ -216,6 +216,33 @@ describe('ChannelsService', () => {
|
||||
expect(tx.smsSignature.update).toHaveBeenCalledWith({ where: { id: 'sig-1' }, data: { reportStatus: 'approved' } });
|
||||
});
|
||||
|
||||
it('changes a drainage report task without overwriting the signature report summary', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
const tx = {
|
||||
smsSignature: {
|
||||
findUnique: jest.fn().mockResolvedValue({ id: 'sig-1', tenantId: 'tenant-1', applicationId: 'app-1' }),
|
||||
update: jest.fn(),
|
||||
},
|
||||
smsChannel: { findUnique: jest.fn().mockResolvedValue({ id: 'channel-1', carrier: 'mobile', status: 'active' }) },
|
||||
channelSignatureReportTask: {
|
||||
findFirst: jest.fn().mockResolvedValue({ id: 'drainage-task-1', signatureId: 'sig-1', channelId: 'channel-1', reportType: 'drainage', drainageItemId: 'drain-1', status: 'reporting' }),
|
||||
update: jest.fn().mockResolvedValue({ id: 'drainage-task-1', status: 'approved' }),
|
||||
create: jest.fn(),
|
||||
},
|
||||
channelSignatureReportRecord: { create: jest.fn().mockResolvedValue({ id: 'record-1' }) },
|
||||
};
|
||||
prisma.$transaction.mockImplementation((callback) => callback(tx));
|
||||
const service = new ChannelsService(prisma as never);
|
||||
|
||||
await expect(service.changeReportTaskStatuses({
|
||||
items: [{ signatureId: 'sig-1', channelId: 'channel-1', reportType: 'drainage', drainageItemId: 'drain-1', status: 'approved' }],
|
||||
reason: '引流信息已报备',
|
||||
})).resolves.toEqual([{ signatureId: 'sig-1', reportType: 'drainage', drainageItemId: 'drain-1', channelId: 'channel-1', status: 'approved' }]);
|
||||
expect(tx.channelSignatureReportTask.findFirst).toHaveBeenCalledWith({ where: { signatureId: 'sig-1', channelId: 'channel-1', reportType: 'drainage', drainageItemId: 'drain-1' } });
|
||||
expect(tx.channelSignatureReportRecord.create).toHaveBeenCalledWith({ data: expect.objectContaining({ action: 'manual_status_change', statusBefore: 'reporting', statusAfter: 'approved' }) });
|
||||
expect(tx.smsSignature.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockQueueAdd.mockClear();
|
||||
mockQueueClose.mockClear();
|
||||
|
||||
@@ -98,11 +98,13 @@ export interface CreateReportTaskDto {
|
||||
tenantId: string;
|
||||
signatureId: string;
|
||||
channelId: string;
|
||||
reportType?: 'signature' | 'drainage';
|
||||
drainageItemId?: string;
|
||||
createdById?: string;
|
||||
}
|
||||
|
||||
export interface ChangeReportTaskStatusesDto {
|
||||
items: Array<{ signatureId: string; channelId: string; status: string }>;
|
||||
items: Array<{ signatureId: string; channelId: string; status: string; reportType?: 'signature' | 'drainage'; drainageItemId?: string }>;
|
||||
reason?: string;
|
||||
operatorId?: string;
|
||||
}
|
||||
@@ -973,11 +975,15 @@ export class ChannelsService implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
|
||||
async createReportTask(data: CreateReportTaskDto) {
|
||||
const reportType = data.reportType ?? 'signature';
|
||||
if (reportType === 'drainage' && !data.drainageItemId) throw new BadRequestException('drainageItemId is required');
|
||||
const task = await this.prisma.channelSignatureReportTask.create({
|
||||
data: {
|
||||
tenantId: data.tenantId,
|
||||
signatureId: data.signatureId,
|
||||
channelId: data.channelId,
|
||||
reportType,
|
||||
drainageItemId: reportType === 'drainage' ? data.drainageItemId : undefined,
|
||||
createdById: data.createdById,
|
||||
status: 'pending',
|
||||
},
|
||||
@@ -993,20 +999,24 @@ export class ChannelsService implements OnModuleInit, OnModuleDestroy {
|
||||
if (!allowed.has(item.status)) throw new BadRequestException('unsupported report task status');
|
||||
}
|
||||
return this.prisma.$transaction(async (tx) => {
|
||||
const signatureIds = [...new Set(data.items.map((item) => item.signatureId))];
|
||||
const signatureIds = [...new Set(data.items.filter((item) => (item.reportType ?? 'signature') === 'signature').map((item) => item.signatureId))];
|
||||
const drainageResults: Array<{ signatureId: string; reportType: 'drainage'; drainageItemId: string; channelId: string; status: string }> = [];
|
||||
for (const item of data.items) {
|
||||
const reportType = item.reportType ?? 'signature';
|
||||
if (reportType === 'drainage' && !item.drainageItemId) throw new BadRequestException('drainageItemId is required');
|
||||
const signature = await tx.smsSignature.findUnique({ where: { id: item.signatureId } });
|
||||
const channel = await tx.smsChannel.findUnique({ where: { id: item.channelId } });
|
||||
if (!signature || !channel) throw new NotFoundException('Signature or channel not found');
|
||||
const existing = await tx.channelSignatureReportTask.findFirst({ where: { signatureId: item.signatureId, channelId: item.channelId } });
|
||||
const existing = await tx.channelSignatureReportTask.findFirst({ where: { signatureId: item.signatureId, channelId: item.channelId, reportType, drainageItemId: reportType === 'drainage' ? item.drainageItemId : null } });
|
||||
const task = existing
|
||||
? await tx.channelSignatureReportTask.update({ where: { id: existing.id }, data: { status: item.status, reason: data.reason } })
|
||||
: await tx.channelSignatureReportTask.create({ data: { tenantId: signature.tenantId, signatureId: item.signatureId, channelId: item.channelId, status: item.status, reason: data.reason, createdById: data.operatorId } });
|
||||
: await tx.channelSignatureReportTask.create({ data: { tenantId: signature.tenantId, signatureId: item.signatureId, channelId: item.channelId, reportType, drainageItemId: reportType === 'drainage' ? item.drainageItemId : undefined, status: item.status, reason: data.reason, createdById: data.operatorId } });
|
||||
await tx.channelSignatureReportRecord.create({ data: { taskId: task.id, channelId: item.channelId, action: 'manual_status_change', statusBefore: existing?.status, statusAfter: item.status, reason: data.reason, operatorId: data.operatorId } });
|
||||
if (reportType === 'drainage') drainageResults.push({ signatureId: item.signatureId, reportType, drainageItemId: item.drainageItemId!, channelId: item.channelId, status: item.status });
|
||||
}
|
||||
const summaries = [];
|
||||
for (const signatureId of signatureIds) summaries.push(await this.recomputeSignatureReportSummary(tx, signatureId));
|
||||
return summaries;
|
||||
return [...summaries, ...drainageResults];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1018,7 +1028,7 @@ export class ChannelsService implements OnModuleInit, OnModuleDestroy {
|
||||
include: { group: { include: { items: { include: { channel: true } } } } },
|
||||
}) : [];
|
||||
const configuredChannels = routes.flatMap((route) => route.group.items.map((item) => item.channel)).filter((channel) => channel.status !== 'deleted');
|
||||
const tasks = await tx.channelSignatureReportTask.findMany({ where: { signatureId }, include: { channel: true } });
|
||||
const tasks = await tx.channelSignatureReportTask.findMany({ where: { signatureId, reportType: 'signature' }, include: { channel: true } });
|
||||
const channels = configuredChannels.length ? configuredChannels : tasks.map((task) => task.channel);
|
||||
const uniqueChannels = [...new Map(channels.map((channel) => [channel.id, channel])).values()];
|
||||
const taskByChannel = new Map(tasks.map((task) => [task.channelId, task]));
|
||||
@@ -1067,7 +1077,9 @@ export class ChannelsService implements OnModuleInit, OnModuleDestroy {
|
||||
},
|
||||
});
|
||||
await this.updateReportTaskStatus(taskId, task.channelId, task.status, statusAfter, 'receipt_import', data.reason);
|
||||
await this.recomputeSignatureReportSummary(this.prisma as unknown as Prisma.TransactionClient, task.signatureId);
|
||||
if ((task.reportType ?? 'signature') === 'signature') {
|
||||
await this.recomputeSignatureReportSummary(this.prisma as unknown as Prisma.TransactionClient, task.signatureId);
|
||||
}
|
||||
return imported;
|
||||
}
|
||||
|
||||
|
||||
@@ -739,6 +739,7 @@ describe('SendChainService', () => {
|
||||
service['getGatewayQueue'] = jest.fn().mockReturnValue({ add: gatewayAdd });
|
||||
|
||||
await expect(service.processSendJob({ messageRecordId: 'record-1' })).resolves.toEqual(expect.objectContaining({ submitted: true, channelId: backup.id }));
|
||||
expect(prisma.channelSignatureReportTask.findMany).toHaveBeenCalledWith(expect.objectContaining({ where: expect.objectContaining({ reportType: 'signature' }) }));
|
||||
expect(prisma.smsMessageRecord.update).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ channelId: backup.id }) }));
|
||||
});
|
||||
|
||||
|
||||
@@ -1876,7 +1876,7 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
||||
const signatureId = await this.resolveMessageSignatureId(message);
|
||||
if (!signatureId) throw new BadRequestException('短信签名未配置,无法选择已报备通道');
|
||||
const approvedTasks = await this.prisma.channelSignatureReportTask.findMany({
|
||||
where: { signatureId, status: 'approved', channelId: { in: route.group.items.map((item) => item.channelId) } },
|
||||
where: { signatureId, reportType: 'signature', status: 'approved', channelId: { in: route.group.items.map((item) => item.channelId) } },
|
||||
select: { channelId: true },
|
||||
});
|
||||
const approvedChannelIds = new Set(approvedTasks.map((task) => task.channelId));
|
||||
@@ -2247,7 +2247,7 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
||||
throw new BadRequestException('短信签名未配置,不能提交到通道');
|
||||
}
|
||||
const reportTask = await this.prisma.channelSignatureReportTask.findFirst({
|
||||
where: { signatureId, channelId, status: 'approved' },
|
||||
where: { signatureId, channelId, reportType: 'signature', status: 'approved' },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!reportTask) {
|
||||
|
||||
@@ -54,7 +54,7 @@ export class AdminSmsConfigController {
|
||||
|
||||
@Post('enterprise-signatures')
|
||||
createSignature(@Body() body: CreateSmsSignatureDto) {
|
||||
return this.smsConfig.createSignature(body);
|
||||
return this.smsConfig.createSignature(body, { initialAuditStatus: 'approved' });
|
||||
}
|
||||
|
||||
@Put('enterprise-signatures/:id')
|
||||
|
||||
@@ -66,6 +66,7 @@ function createPrismaMock() {
|
||||
materials: [],
|
||||
}]),
|
||||
findUnique: jest.fn().mockResolvedValue({ id: 'sig-1', tenantId: 'tenant-1', auditStatus: 'pending' }),
|
||||
create: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'sig-new', tenantId: 'tenant-1', ...data })),
|
||||
update: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'sig-1', tenantId: 'tenant-1', ...data })),
|
||||
},
|
||||
signatureReportMaterial: {
|
||||
@@ -75,6 +76,14 @@ function createPrismaMock() {
|
||||
upsert: jest.fn().mockResolvedValue({ id: 'drainage-report-value-1' }),
|
||||
deleteMany: jest.fn().mockResolvedValue({ count: 0 }),
|
||||
},
|
||||
channelSignatureReportTask: {
|
||||
findFirst: jest.fn().mockResolvedValue(null),
|
||||
create: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'drainage-task-1', ...data })),
|
||||
deleteMany: jest.fn().mockResolvedValue({ count: 0 }),
|
||||
},
|
||||
channelSignatureReportRecord: {
|
||||
create: jest.fn().mockResolvedValue({ id: 'drainage-record-1' }),
|
||||
},
|
||||
smsTemplate: {
|
||||
findMany: jest.fn().mockResolvedValue([{
|
||||
id: 'tpl-1',
|
||||
@@ -502,6 +511,24 @@ describe('SmsConfigService', () => {
|
||||
expect(prisma.drainageReportMaterial.deleteMany).toHaveBeenCalledWith({
|
||||
where: { signatureId: 'sig-1', drainageItemId: { notIn: ['drain-1'] } },
|
||||
});
|
||||
expect(prisma.channelSignatureReportTask.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({ signatureId: 'sig-1', channelId: 'channel-1', reportType: 'drainage', drainageItemId: 'drain-1', status: 'pending' }),
|
||||
});
|
||||
expect(prisma.channelSignatureReportRecord.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({ taskId: 'drainage-task-1', action: 'create', statusAfter: 'pending' }),
|
||||
});
|
||||
});
|
||||
|
||||
it('creates admin signatures with an approved initial audit status', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
|
||||
await service.createSignature({ tenantId: 'tenant-1', name: '运营新建签名' }, { initialAuditStatus: 'approved' });
|
||||
|
||||
expect(prisma.smsSignature.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({ auditStatus: 'approved', name: '运营新建签名' }),
|
||||
});
|
||||
expect(prisma.auditRecord.create).toHaveBeenCalledWith({ data: expect.objectContaining({ action: 'admin_create_approved', statusAfter: 'approved' }) });
|
||||
});
|
||||
|
||||
it('updates enterprise signature drainage info through the admin API path', async () => {
|
||||
|
||||
@@ -44,6 +44,10 @@ export interface CreateSmsSignatureDto {
|
||||
drainageInfo?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface CreateSmsSignatureOptions {
|
||||
initialAuditStatus?: string;
|
||||
}
|
||||
|
||||
export type UpdateSmsSignatureDto = Partial<Omit<CreateSmsSignatureDto, 'tenantId'>> & {
|
||||
auditStatus?: string;
|
||||
};
|
||||
@@ -589,19 +593,44 @@ export class SmsConfigService {
|
||||
const applicationIds = signatures.map((signature) => signature.applicationId).filter((id): id is string => Boolean(id));
|
||||
const routes = applicationIds.length ? await this.prisma.channelRouteRule.findMany({
|
||||
where: { applicationId: { in: applicationIds }, status: 'active' },
|
||||
include: { group: { include: { items: { include: { channel: true } } } } },
|
||||
include: { group: { include: { items: { include: { channel: { include: { reportFields: true } } } } } } },
|
||||
}) : [];
|
||||
return signatures.map((signature) => ({
|
||||
...signature,
|
||||
reportTargets: (() => {
|
||||
const channels = routes.filter((route) => route.applicationId === signature.applicationId && route.group).flatMap((route) => route.group!.items.map((item) => item.channel)).filter((channel) => channel.status !== 'deleted');
|
||||
const taskByChannel = new Map((signature.reportTasks ?? []).map((task) => [task.channelId, task]));
|
||||
const taskByChannel = new Map((signature.reportTasks ?? []).filter((task) => task.reportType === 'signature').map((task) => [task.channelId, task]));
|
||||
return [...new Map(channels.map((channel) => [channel.id, channel])).values()].map((channel) => ({ channel, channelId: channel.id, status: taskByChannel.get(channel.id)?.status ?? 'pending', taskId: taskByChannel.get(channel.id)?.id }));
|
||||
})(),
|
||||
drainageReportTargets: Object.fromEntries((Array.isArray((signature.drainageInfo as Record<string, unknown> | null)?.links) ? (signature.drainageInfo as Record<string, unknown>).links as Array<Record<string, unknown>> : []).map((link) => {
|
||||
const drainageItemId = String(link.id ?? '');
|
||||
const channels = routes
|
||||
.filter((route) => route.applicationId === signature.applicationId && route.group)
|
||||
.flatMap((route) => route.group!.items.map((item) => item.channel))
|
||||
.filter((channel) => channel.status !== 'deleted' && channel.reportFields.some((field) => field.status === 'active' && ['drainage', 'both'].includes(field.reportType)));
|
||||
const taskByChannel = new Map((signature.reportTasks ?? []).filter((task) => task.reportType === 'drainage' && task.drainageItemId === drainageItemId).map((task) => [task.channelId, task]));
|
||||
return [drainageItemId, [...new Map(channels.map((channel) => [channel.id, channel])).values()].map((channel) => ({ channel, channelId: channel.id, status: taskByChannel.get(channel.id)?.status ?? 'pending', taskId: taskByChannel.get(channel.id)?.id }))];
|
||||
})),
|
||||
drainageCarrierReportSummary: Object.fromEntries((Array.isArray((signature.drainageInfo as Record<string, unknown> | null)?.links) ? (signature.drainageInfo as Record<string, unknown>).links as Array<Record<string, unknown>> : []).map((link) => {
|
||||
const drainageItemId = String(link.id ?? '');
|
||||
const channels = routes
|
||||
.filter((route) => route.applicationId === signature.applicationId && route.group)
|
||||
.flatMap((route) => route.group!.items.map((item) => item.channel))
|
||||
.filter((channel) => channel.status !== 'deleted' && channel.reportFields.some((field) => field.status === 'active' && ['drainage', 'both'].includes(field.reportType)));
|
||||
const targets = [...new Map(channels.map((channel) => [channel.id, channel])).values()];
|
||||
const taskByChannel = new Map((signature.reportTasks ?? []).filter((task) => task.reportType === 'drainage' && task.drainageItemId === drainageItemId).map((task) => [task.channelId, task]));
|
||||
return [drainageItemId, Object.fromEntries(['mobile', 'unicom', 'telecom'].map((carrier) => {
|
||||
const carrierTargets = targets.filter((channel) => channel.carrier === carrier || channel.carrier === 'all');
|
||||
const statuses = carrierTargets.map((channel) => taskByChannel.get(channel.id)?.status ?? 'pending');
|
||||
const approved = statuses.filter((status) => status === 'approved').length;
|
||||
const status = !statuses.length ? 'not_applicable' : approved === statuses.length ? 'approved' : statuses.some((item) => ['failed', 'rejected'].includes(item)) ? 'failed' : statuses.some((item) => ['reporting', 'exporting'].includes(item)) || approved ? 'reporting' : statuses.some((item) => item === 'waiting_material') ? 'waiting_material' : 'pending';
|
||||
return [carrier, { status, approved, total: statuses.length }];
|
||||
}))];
|
||||
})),
|
||||
carrierReportSummary: Object.fromEntries(['mobile', 'unicom', 'telecom'].map((carrier) => {
|
||||
const configured = routes.filter((route) => route.applicationId === signature.applicationId && route.group).flatMap((route) => route.group!.items.map((item) => item.channel)).filter((channel) => channel.status !== 'deleted' && (channel.carrier === carrier || channel.carrier === 'all'));
|
||||
const targets = [...new Map(configured.map((channel) => [channel.id, channel])).values()];
|
||||
const taskByChannel = new Map((signature.reportTasks ?? []).map((task) => [task.channelId, task]));
|
||||
const taskByChannel = new Map((signature.reportTasks ?? []).filter((task) => task.reportType === 'signature').map((task) => [task.channelId, task]));
|
||||
const statuses = targets.map((channel) => taskByChannel.get(channel.id)?.status ?? 'pending');
|
||||
const approved = statuses.filter((status) => status === 'approved').length;
|
||||
const status = !targets.length ? 'not_applicable' : approved === targets.length ? 'approved' : statuses.some((item) => ['failed', 'rejected'].includes(item)) ? 'failed' : statuses.some((item) => ['reporting', 'exporting'].includes(item)) || approved ? 'reporting' : statuses.some((item) => item === 'waiting_material') ? 'waiting_material' : 'pending';
|
||||
@@ -610,7 +639,7 @@ export class SmsConfigService {
|
||||
}));
|
||||
}
|
||||
|
||||
async createSignature(data: CreateSmsSignatureDto) {
|
||||
async createSignature(data: CreateSmsSignatureDto, options: CreateSmsSignatureOptions = {}) {
|
||||
await this.validateSignatureReportValues(data.applicationId, data.drainageInfo);
|
||||
const drainageInfo = await this.withReportRequirementSnapshot(data.applicationId, data.drainageInfo);
|
||||
const signature = await this.prisma.smsSignature.create({
|
||||
@@ -619,10 +648,21 @@ export class SmsConfigService {
|
||||
applicationId: data.applicationId,
|
||||
name: data.name,
|
||||
purpose: data.purpose,
|
||||
auditStatus: options.initialAuditStatus,
|
||||
drainageInfo: drainageInfo as Prisma.InputJsonValue | undefined,
|
||||
},
|
||||
});
|
||||
await this.syncSignatureReportValues(signature.id, data.applicationId, drainageInfo);
|
||||
if (options.initialAuditStatus) {
|
||||
await this.createAuditRecord({
|
||||
tenantId: signature.tenantId,
|
||||
targetType: 'sms_signature',
|
||||
targetId: signature.id,
|
||||
action: 'admin_create_approved',
|
||||
statusAfter: options.initialAuditStatus,
|
||||
reason: '运营端新建签名自动审核通过',
|
||||
});
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
@@ -684,6 +724,13 @@ export class SmsConfigService {
|
||||
...(drainageItemIds.length > 0 ? { drainageItemId: { notIn: drainageItemIds } } : {}),
|
||||
},
|
||||
});
|
||||
await this.prisma.channelSignatureReportTask.deleteMany({
|
||||
where: {
|
||||
signatureId,
|
||||
reportType: 'drainage',
|
||||
...(drainageItemIds.length > 0 ? { drainageItemId: { notIn: drainageItemIds } } : {}),
|
||||
},
|
||||
});
|
||||
for (const field of fields.filter((item) => item.reportTypes.some((type) => type === 'signature' || type === 'both'))) {
|
||||
const value = reportValueParts(signatureValues[field.code]);
|
||||
for (const channel of field.channels) {
|
||||
@@ -694,11 +741,29 @@ export class SmsConfigService {
|
||||
});
|
||||
}
|
||||
}
|
||||
const drainageFields = fields.filter((item) => item.reportTypes.some((type) => type === 'drainage' || type === 'both'));
|
||||
const drainageChannels = new Map(drainageFields.flatMap((item) => item.channels).map((channel) => [channel.id, channel]));
|
||||
const signatureOwner = links.length > 0
|
||||
? await this.prisma.smsSignature.findUnique({ where: { id: signatureId }, select: { tenantId: true } })
|
||||
: null;
|
||||
for (const link of links) {
|
||||
const drainageItemId = String(link.id ?? '');
|
||||
const values = isRecord(link.reportValues) ? link.reportValues : {};
|
||||
if (!drainageItemId) continue;
|
||||
for (const field of fields.filter((item) => item.reportTypes.some((type) => type === 'drainage' || type === 'both'))) {
|
||||
for (const channel of drainageChannels.values()) {
|
||||
const existingTask = await this.prisma.channelSignatureReportTask.findFirst({
|
||||
where: { signatureId, channelId: channel.id, reportType: 'drainage', drainageItemId },
|
||||
});
|
||||
if (!existingTask && signatureOwner) {
|
||||
const task = await this.prisma.channelSignatureReportTask.create({
|
||||
data: { tenantId: signatureOwner.tenantId, signatureId, channelId: channel.id, reportType: 'drainage', drainageItemId, status: 'pending' },
|
||||
});
|
||||
await this.prisma.channelSignatureReportRecord.create({
|
||||
data: { taskId: task.id, channelId: channel.id, action: 'create', statusAfter: 'pending' },
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const field of drainageFields) {
|
||||
const value = reportValueParts(values[field.code]);
|
||||
for (const channel of field.channels) {
|
||||
await this.prisma.drainageReportMaterial.upsert({
|
||||
|
||||
Reference in New Issue
Block a user