fix(任务): 归档以整棵树为单位并移除隐藏中间态
Docker Build and Push / build-image (push) Successful in 1m42s

- 自动归档改为 doneAt + autoArchive 到期,只判定顶层任务,到期整树归档
- 手动归档/恢复提升到根父任务,子任务完全跟随父任务
- 新增 normalizeArchiveTree 修复历史父子归档不一致
- 移除按列保留窗口的显示过滤(前后端实现删除,T-FL-01~15 废止)
- 列 retention 字段仅兼容旧数据保留,不再参与判定
- 已归档查询返回归档子任务,删除提示可正确显示连带条数
This commit is contained in:
jif2.zhang
2026-11-10 09:20:40 +08:00
parent 7b52220aad
commit e05ed6b491
15 changed files with 255 additions and 489 deletions
+3 -12
View File
@@ -17,7 +17,6 @@ import { tool, reqStr, parseMarkDate } from '../helpers.js'
const MAX_NAME = 50
const MAX_COLUMN_NAME = 20
const STATUSES = ['todo', 'doing', 'done']
const RETENTIONS = ['never', 'today', '3d', 'week', 'month', 'forever']
const TASK_INCLUDE = {
column: true,
@@ -294,16 +293,14 @@ function registerColumnTools(server, ctx) {
projectId: z.coerce.number().int().positive().describe('项目 ID'),
name: z.string().describe('列名, 最多 20 字'),
boundStatus: z.enum(STATUSES).nullable().optional().describe('绑定任务状态 todo/doing/done, null 表示不联动'),
retention: z.enum(RETENTIONS).optional().describe('完成保留选项, 默认 forever'),
},
}, async ({ projectId, name, boundStatus = null, retention = 'forever' }) => {
}, async ({ projectId, name, boundStatus = null }) => {
const project = await requireProject(projectId, userId)
const cleanName = reqStr(name, '列名', MAX_COLUMN_NAME)
const cols = await loadColumns(project.id)
if (cols.length >= MAX_COLUMNS) throw new Error(`最多 ${MAX_COLUMNS} 列`)
if (cols.some((c) => c.name === cleanName)) throw new Error('列名已存在')
if (boundStatus !== null && !STATUSES.includes(boundStatus)) throw new Error('绑定状态取值非法')
if (!RETENTIONS.includes(retention)) throw new Error('保留选项取值非法')
const row = await prisma.boardColumn.create({
data: {
@@ -312,7 +309,6 @@ function registerColumnTools(server, ctx) {
name: cleanName,
sortOrder: cols.length ? Math.max(...cols.map((c) => c.sortOrder)) + 1 : 0,
boundStatus,
retention,
},
})
return { item: row }
@@ -320,14 +316,13 @@ function registerColumnTools(server, ctx) {
tool(server, 'update_column', {
title: '修改看板列',
description: '修改列名、绑定状态或完成保留选项。',
description: '修改列名或绑定状态。',
inputSchema: {
columnId: z.coerce.number().int().positive().describe('看板列 ID'),
name: z.string().optional().describe('新列名, 最多 20 字'),
boundStatus: z.enum(STATUSES).nullable().optional().describe('绑定任务状态, null 表示取消绑定'),
retention: z.enum(RETENTIONS).optional().describe('完成保留选项'),
},
}, async ({ columnId, name, boundStatus, retention }) => {
}, async ({ columnId, name, boundStatus }) => {
const col = await prisma.boardColumn.findUnique({ where: { id: Number(columnId) } })
if (!col) throw new Error('列不存在')
const project = await requireProject(col.projectId, userId)
@@ -345,10 +340,6 @@ function registerColumnTools(server, ctx) {
if (boundStatus !== null && !STATUSES.includes(boundStatus)) throw new Error('绑定状态取值非法')
data.boundStatus = boundStatus
}
if (retention !== undefined) {
if (!RETENTIONS.includes(retention)) throw new Error('保留选项取值非法')
data.retention = retention
}
const row = await prisma.boardColumn.update({ where: { id: col.id }, data })
return { item: row }
})