Files
iboard/server/task-domain/copyTask.js
T

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 })),
},
}
}