// 任务模块: 跨项目拷贝 (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 })), }, } }