98 lines
3.4 KiB
JavaScript
98 lines
3.4 KiB
JavaScript
// 任务模块: 跨项目拷贝 (T-CP-*)
|
|
// 拷贝而非移动: 新建记录 + 深拷贝子任务, 源任务零改动.
|
|
import { prisma } from '../db.js'
|
|
import { genUuid, withUuid, withTaskNumber } from './identity.js'
|
|
|
|
/**
|
|
* 拷贝一条任务(含子任务)到目标项目。
|
|
*
|
|
* 规则:
|
|
* - 全字段复制: 标题/描述/状态/列/扩展卡片/标签 (T-CP-02)
|
|
* - 子任务一并复制, 且归属目标项目 (T-CP-03, T-CP-08)
|
|
* - creatorId = 操作者 (T-CP-04)
|
|
* - 负责人 / 任务成员只保留"属于目标项目成员"的部分, 其余丢弃并回报 (T-CP-07, T-EX-08)
|
|
* - 状态与列沿用源值, 不强行纠正 (T-EX-14)
|
|
*
|
|
* @returns {{ task, dropped: { assignee: null|{id,name}, members: {id,name}[] } }}
|
|
*/
|
|
export async function copyTask({ source, targetProject, targetColumns, operatorId, targetMemberIds }) {
|
|
const memberSet = new Set(targetMemberIds)
|
|
|
|
// 列映射: 目标项目里找同名列, 找不到就用第一列 (保证 columnId 合法)
|
|
const pickColumn = (sourceColumnId) => {
|
|
const srcCol = source.__columns?.find((c) => c.id === sourceColumnId)
|
|
if (srcCol) {
|
|
const same = targetColumns.find((c) => c.name === srcCol.name)
|
|
if (same) return same.id
|
|
}
|
|
return targetColumns[0]?.id
|
|
}
|
|
|
|
// 人员过滤
|
|
const keepAssignee = source.assigneeId && memberSet.has(source.assigneeId) ? source.assigneeId : null
|
|
const droppedMembers = []
|
|
const keptMemberIds = []
|
|
for (const m of source.members || []) {
|
|
if (memberSet.has(m.userId)) keptMemberIds.push(m.userId)
|
|
else droppedMembers.push(m.user)
|
|
}
|
|
const droppedAssignee = source.assigneeId && !memberSet.has(source.assigneeId) ? source.assignee : null
|
|
|
|
const baseData = (extra) => ({
|
|
uuid: genUuid(),
|
|
projectId: targetProject.id,
|
|
title: source.title,
|
|
description: source.description || '',
|
|
status: source.status,
|
|
columnId: extra.columnId,
|
|
parentId: extra.parentId ?? null,
|
|
assigneeId: extra.parentId ? (source.assigneeId && memberSet.has(source.assigneeId) ? source.assigneeId : null) : keepAssignee,
|
|
creatorId: operatorId,
|
|
extras: source.extras ?? [],
|
|
doneAt: source.status === 'done' ? (source.doneAt || new Date()) : null,
|
|
sortOrder: source.sortOrder ?? 0,
|
|
tags: {
|
|
create: withUuid((source.tags || []).map((t) => ({ name: t.name }))),
|
|
},
|
|
})
|
|
|
|
const { value: newTask } = await withTaskNumber(targetProject.id, (number) =>
|
|
prisma.task.create({
|
|
data: {
|
|
...baseData({ columnId: pickColumn(source.columnId) }),
|
|
number,
|
|
members: {
|
|
create: withUuid(keptMemberIds.map((uid) => ({ userId: uid }))),
|
|
},
|
|
},
|
|
}),
|
|
)
|
|
|
|
// 子任务: 同法复制, parentId 指向新父任务
|
|
for (const child of source.children || []) {
|
|
const childMembers = (child.members || [])
|
|
.filter((m) => memberSet.has(m.userId))
|
|
.map((m) => m.userId)
|
|
await withTaskNumber(targetProject.id, (number) =>
|
|
prisma.task.create({
|
|
data: {
|
|
...baseData({
|
|
columnId: pickColumn(child.columnId),
|
|
parentId: newTask.id,
|
|
}),
|
|
number,
|
|
members: { create: withUuid(childMembers.map((uid) => ({ userId: uid }))) },
|
|
},
|
|
}),
|
|
)
|
|
}
|
|
|
|
return {
|
|
task: newTask,
|
|
dropped: {
|
|
assignee: droppedAssignee ? { id: droppedAssignee.id, name: droppedAssignee.name } : null,
|
|
members: droppedMembers.map((u) => ({ id: u.id, name: u.name })),
|
|
},
|
|
}
|
|
}
|