v3.5.6版本迭代
CI / verify (pull_request) Waiting to run

This commit is contained in:
2026-08-23 16:41:22 +08:00
parent 608e3e5ec5
commit 7e12eb3ca6
33 changed files with 1537 additions and 278 deletions
@@ -59,6 +59,26 @@ import type {
UpdateDevPlanVersionDto,
} from './dto/dev-plan.dto';
import {
buildDevPlanDocx,
buildDevPlanExportFilename,
buildDevPlanMarkdown,
buildDevPlanPdf,
buildDevPlanXlsx,
devPlanExportMimeType,
mapTaskToExportRow,
type DevPlanExportFormat,
} from './admin-dev-plan-export.util';
function parseAttachmentUrls(value: unknown): string[] {
if (!Array.isArray(value)) return [];
return value.map((u) => String(u).trim()).filter(Boolean);
}
function attachmentUrlsInput(urls?: string[]): Prisma.InputJsonValue | undefined {
const list = (urls ?? []).map((u) => u.trim()).filter(Boolean);
return list.length ? (list as unknown as Prisma.InputJsonValue) : undefined;
}
@@ -132,6 +152,8 @@ export class DevPlanService {
completedAt: Date | null;
attachmentUrls?: unknown;
},
extras?: { creatorName?: string | null; supportTicketNo?: string | null },
@@ -164,6 +186,8 @@ export class DevPlanService {
completedAt: row.completedAt?.toISOString() ?? null,
attachmentUrls: parseAttachmentUrls(row.attachmentUrls),
};
}
@@ -378,6 +402,8 @@ export class DevPlanService {
supportTicketId: dto.supportTicketId ? BigInt(dto.supportTicketId) : null,
attachmentUrls: attachmentUrlsInput(dto.attachmentUrls),
},
});
@@ -400,6 +426,12 @@ export class DevPlanService {
if (!tasks.length) throw new BadRequestException('至少创建 1 条开发任务');
const ticket = await this.prisma.commonSupportTicket.findUnique({
where: { id: ticketId },
select: { attachmentUrls: true },
});
const ticketUrls = parseAttachmentUrls(ticket?.attachmentUrls);
const created = await this.prisma.$transaction(
tasks.map((t) =>
@@ -418,6 +450,10 @@ export class DevPlanService {
supportTicketId: ticketId,
attachmentUrls: attachmentUrlsInput(
t.attachmentUrls?.length ? t.attachmentUrls : ticketUrls,
),
},
}),
@@ -464,6 +500,10 @@ export class DevPlanService {
}
if (dto.attachmentUrls !== undefined) {
data.attachmentUrls = attachmentUrlsInput(dto.attachmentUrls);
}
await this.prisma.devPlanTask.update({ where: { id }, data });
@@ -1157,6 +1197,114 @@ export class DevPlanService {
}
private buildTaskExportWhere(query: {
status?: string;
type?: string;
keyword?: string;
}): Prisma.DevPlanTaskWhereInput {
const where: Prisma.DevPlanTaskWhereInput = {};
if (query.status) where.status = query.status as DevPlanTaskStatus;
if (query.type) where.type = query.type as DevPlanTaskType;
if (query.keyword?.trim()) {
where.OR = [
{ taskNo: { contains: query.keyword.trim() } },
{ content: { contains: query.keyword.trim() } },
];
}
return where;
}
async exportTasks(dto: {
scope: 'filter' | 'selected';
format: DevPlanExportFormat;
ids?: string[];
status?: string;
type?: string;
keyword?: string;
}) {
let rows: Array<{
id: bigint;
taskNo: string;
content: string;
type: DevPlanTaskType;
status: DevPlanTaskStatus;
creatorHqAccountId: bigint;
supportTicketId: bigint | null;
attachmentUrls: unknown;
createdAt: Date;
completedAt: Date | null;
}>;
if (dto.scope === 'selected') {
if (!dto.ids?.length) throw new BadRequestException('请先勾选要导出的任务');
const ids = dto.ids.map(BigInt);
rows = await this.prisma.devPlanTask.findMany({
where: { id: { in: ids } },
orderBy: { createdAt: 'desc' },
});
if (rows.length !== ids.length) throw new BadRequestException('部分任务不存在');
} else {
rows = await this.prisma.devPlanTask.findMany({
where: this.buildTaskExportWhere(dto),
orderBy: { createdAt: 'desc' },
});
}
if (!rows.length) throw new BadRequestException('没有可导出的任务');
const creatorIds = rows.map((r) => r.creatorHqAccountId);
const ticketIds = rows.map((r) => r.supportTicketId).filter((id): id is bigint => id != null);
const [names, tickets] = await Promise.all([
this.loadHqNames(creatorIds),
ticketIds.length
? this.prisma.commonSupportTicket.findMany({
where: { id: { in: ticketIds } },
select: { id: true, ticketNo: true },
})
: Promise.resolve([]),
]);
const ticketMap = new Map(tickets.map((t) => [String(t.id), t.ticketNo] as [string, string]));
const exportRows = rows.map((r) =>
mapTaskToExportRow({
taskNo: r.taskNo,
type: r.type,
status: r.status,
content: r.content,
supportTicketNo: r.supportTicketId != null ? ticketMap.get(String(r.supportTicketId)) ?? null : null,
creatorName: names.get(String(r.creatorHqAccountId)) ?? null,
createdAt: r.createdAt.toISOString(),
completedAt: r.completedAt?.toISOString() ?? null,
attachmentUrls: parseAttachmentUrls(r.attachmentUrls),
}),
);
let buffer: Buffer;
switch (dto.format) {
case 'markdown':
buffer = buildDevPlanMarkdown(exportRows);
break;
case 'docx':
buffer = await buildDevPlanDocx(exportRows);
break;
case 'xlsx':
buffer = await buildDevPlanXlsx(exportRows);
break;
case 'pdf':
buffer = await buildDevPlanPdf(exportRows);
break;
default:
throw new BadRequestException('不支持的导出格式');
}
return {
filename: buildDevPlanExportFilename(dto.format, exportRows.length),
mimeType: devPlanExportMimeType(dto.format),
contentBase64: buffer.toString('base64'),
count: exportRows.length,
};
}
}