feat(ops): v4.0.4 开发任务多行与版本状态同步、合伙人/门店删除、账单合伙人列

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-31 16:08:55 +08:00
parent e26206b93c
commit ac94f5f5de
15 changed files with 568 additions and 59 deletions
@@ -32,7 +32,11 @@ import type {
} from '@dukang/shared-types';
import { taskCompletedAtOnStatus, versionStatusTimestamps } from '@dukang/domain';
import {
resolveTaskStatusFromVersions,
taskCompletedAtOnStatus,
versionStatusTimestamps,
} from '@dukang/domain';
import { PrismaService } from '../../common/prisma/prisma.module';
@@ -530,6 +534,8 @@ export class DevPlanService {
await this.replaceTaskVersions(id, dto.versionIds);
}
await this.syncLinkedTaskStatuses([id]);
return this.getTask(id);
}
@@ -768,8 +774,10 @@ export class DevPlanService {
}
await this.syncLinkedTasksForVersion(row.id);
if (status === 'RELEASED') {
await this.cascadeVersionReleased(row.id, row.versionNo);
await this.cascadeTicketsPublished(row.id, row.versionNo);
}
return this.getVersion(row.id);
@@ -840,15 +848,16 @@ export class DevPlanService {
await this.prisma.devPlanVersion.update({ where: { id }, data });
if (
dto.status === 'RELEASED' &&
existing.status !== 'RELEASED'
) {
const versionNo = (dto.versionNo ?? existing.versionNo).trim();
await this.cascadeVersionReleased(id, versionNo);
if (dto.taskIds != null) await this.replaceVersionTasks(id, dto.taskIds);
if (dto.status != null || dto.taskIds != null) {
await this.syncLinkedTasksForVersion(id);
}
if (dto.taskIds != null) await this.replaceVersionTasks(id, dto.taskIds);
if (dto.status === 'RELEASED' && existing.status !== 'RELEASED') {
const versionNo = (dto.versionNo ?? existing.versionNo).trim();
await this.cascadeTicketsPublished(id, versionNo);
}
return this.getVersion(id);
@@ -880,11 +889,58 @@ export class DevPlanService {
await this.replaceVersionTasks(versionId, taskIds);
await this.syncLinkedTasksForVersion(versionId);
return this.getVersion(versionId);
}
private async cascadeVersionReleased(versionId: bigint, versionNo: string) {
private async syncLinkedTasksForVersion(versionId: bigint) {
const links = await this.prisma.devPlanVersionTask.findMany({
where: { versionId },
select: { taskId: true },
});
await this.syncLinkedTaskStatuses(links.map((l) => l.taskId));
}
private async syncLinkedTaskStatuses(taskIds: bigint[]) {
const uniq = [...new Set(taskIds)];
if (!uniq.length) return;
const [tasks, links] = await Promise.all([
this.prisma.devPlanTask.findMany({
where: { id: { in: uniq } },
select: { id: true, status: true, completedAt: true },
}),
this.prisma.devPlanVersionTask.findMany({
where: { taskId: { in: uniq } },
include: { version: { select: { status: true } } },
}),
]);
const statusesByTask = new Map<string, DevPlanVersionStatus[]>();
for (const link of links) {
const key = String(link.taskId);
const list = statusesByTask.get(key) ?? [];
list.push(link.version.status);
statusesByTask.set(key, list);
}
for (const task of tasks) {
const next = resolveTaskStatusFromVersions(statusesByTask.get(String(task.id)) ?? []);
if (!next || next === task.status) continue;
const completed = taskCompletedAtOnStatus(task.status, next, task.completedAt);
await this.prisma.devPlanTask.update({
where: { id: task.id },
data: {
status: next,
...(completed ? { completedAt: completed } : {}),
},
});
}
}
private async cascadeTicketsPublished(versionId: bigint, versionNo: string) {
const links = await this.prisma.devPlanVersionTask.findMany({
where: { versionId },
select: { taskId: true },
@@ -894,33 +950,26 @@ export class DevPlanService {
const taskIds = links.map((l) => l.taskId);
const now = new Date();
await this.prisma.$transaction(async (tx) => {
await tx.devPlanTask.updateMany({
where: { id: { in: taskIds }, status: { not: 'RELEASED' } },
data: { status: 'RELEASED', completedAt: now },
});
const tasks = await this.prisma.devPlanTask.findMany({
where: { id: { in: taskIds }, supportTicketId: { not: null } },
select: { supportTicketId: true },
});
const ticketIds = [
...new Set(
tasks
.map((t) => t.supportTicketId)
.filter((id): id is bigint => id != null),
),
];
if (!ticketIds.length) return;
const tasks = await tx.devPlanTask.findMany({
where: { id: { in: taskIds }, supportTicketId: { not: null } },
select: { supportTicketId: true },
});
const ticketIds = [
...new Set(
tasks
.map((t) => t.supportTicketId)
.filter((id): id is bigint => id != null),
),
];
if (!ticketIds.length) return;
await tx.commonSupportTicket.updateMany({
where: { id: { in: ticketIds }, status: { not: 'REJECTED' } },
data: {
status: 'PUBLISHED',
releasedVersionNo: versionNo,
publishedAt: now,
},
});
await this.prisma.commonSupportTicket.updateMany({
where: { id: { in: ticketIds }, status: { not: 'REJECTED' } },
data: {
status: 'PUBLISHED',
releasedVersionNo: versionNo,
publishedAt: now,
},
});
}
@@ -976,6 +1025,8 @@ export class DevPlanService {
await this.addVersionTasks(versionId, taskIds);
await this.syncLinkedTasksForVersion(versionId);
return this.getVersion(versionId);
}
@@ -1248,7 +1299,7 @@ export class DevPlanService {
}
await this.syncLinkedTaskStatuses(taskIds);
return { updated: taskIds.length };