@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user