feat(V0.2): 任务模块 —— 项目 / 列表与看板 / 列-状态绑定 / 一级子任务
Docker Build and Push / build-image (push) Failing after 3m51s
Docker Build and Push / build-image (push) Failing after 3m51s
需求 142 条 (T-*), 设计见 docs/V0.2/任务模块/ 数据层: - 新增 6 张表: Project / ProjectMember / BoardColumn / Task / TaskMember / TaskTag - 看板列是实体(可增删改排序), Task.columnId 外键 RESTRICT 防误删列丢任务 - Task.status(todo/doing/done) 与 columnId 双落库, 二者正交 领域层 server/task-domain/ (唯一口径, 前后端不重复实现): - resolveStatus: 列决定状态(只看目标列, 无拖出回退); 状态与列不匹配时按列序取第一个绑定该状态的列自动归位, 无匹配列则不移动; 子任务完全豁免 - retention: 逐列 6 档保留窗口(永不显示/当天/3天/本周/本月/永久显示), 以 doneAt 判定 - copyTask: 跨项目深拷贝, 人员按目标项目成员过滤并回报丢弃名单 接口: /api/projects, /api/columns, /api/tasks (均校验项目成员) 前端: - 底部 Tab 插入「任务」, 位于「打卡」之后; 默认首页仍为打卡 - 列表视图(子任务缩进+折叠) / 看板视图(长按拖拽换列、拖到卡片降为子任务) - 列设置(列管理+状态绑定+保留选项) / 任务详情(扩展信息卡片默认收起) / 成员管理 - 视图记忆与上次项目存设备端 已验证: 95 文件语法 0 错误、相对导入 0 缺失、领域逻辑用例全部通过
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
-- 任务模块 (V0.2): 项目 / 成员 / 看板列 / 任务 / 任务成员 / 标签
|
||||
-- 设计口径见 docs/V0.2/任务模块/任务模块数据库设计文档.md
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Project` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(50) NOT NULL,
|
||||
`ownerId` INTEGER NOT NULL,
|
||||
`isPersonal` BOOLEAN NOT NULL DEFAULT false,
|
||||
`archived` BOOLEAN NOT NULL DEFAULT false,
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updatedAt` DATETIME(3) NOT NULL,
|
||||
|
||||
INDEX `Project_ownerId_idx`(`ownerId`),
|
||||
INDEX `Project_archived_idx`(`archived`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `ProjectMember` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`projectId` INTEGER NOT NULL,
|
||||
`userId` INTEGER NOT NULL,
|
||||
`permission` VARCHAR(191) NOT NULL DEFAULT 'member',
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
|
||||
UNIQUE INDEX `ProjectMember_projectId_userId_key`(`projectId`, `userId`),
|
||||
INDEX `ProjectMember_userId_idx`(`userId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `BoardColumn` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`projectId` INTEGER NOT NULL,
|
||||
`name` VARCHAR(20) NOT NULL,
|
||||
`sortOrder` INTEGER NOT NULL DEFAULT 0,
|
||||
`boundStatus` VARCHAR(8) NULL,
|
||||
`retention` VARCHAR(8) NOT NULL DEFAULT 'forever',
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updatedAt` DATETIME(3) NOT NULL,
|
||||
|
||||
UNIQUE INDEX `BoardColumn_projectId_name_key`(`projectId`, `name`),
|
||||
INDEX `BoardColumn_projectId_sortOrder_idx`(`projectId`, `sortOrder`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Task` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`projectId` INTEGER NOT NULL,
|
||||
`columnId` INTEGER NOT NULL,
|
||||
`parentId` INTEGER NULL,
|
||||
`title` VARCHAR(200) NOT NULL,
|
||||
`description` TEXT NOT NULL,
|
||||
`status` VARCHAR(8) NOT NULL DEFAULT 'todo',
|
||||
`doneAt` DATETIME(3) NULL,
|
||||
`assigneeId` INTEGER NULL,
|
||||
`creatorId` INTEGER NOT NULL,
|
||||
`extras` JSON NOT NULL,
|
||||
`sortOrder` INTEGER NOT NULL DEFAULT 0,
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updatedAt` DATETIME(3) NOT NULL,
|
||||
|
||||
INDEX `Task_projectId_columnId_idx`(`projectId`, `columnId`),
|
||||
INDEX `Task_projectId_status_idx`(`projectId`, `status`),
|
||||
INDEX `Task_parentId_idx`(`parentId`),
|
||||
INDEX `Task_projectId_doneAt_idx`(`projectId`, `doneAt`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `TaskMember` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`taskId` INTEGER NOT NULL,
|
||||
`userId` INTEGER NOT NULL,
|
||||
|
||||
UNIQUE INDEX `TaskMember_taskId_userId_key`(`taskId`, `userId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `TaskTag` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`taskId` INTEGER NOT NULL,
|
||||
`name` VARCHAR(20) NOT NULL,
|
||||
|
||||
INDEX `TaskTag_taskId_idx`(`taskId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Project` ADD CONSTRAINT `Project_ownerId_fkey` FOREIGN KEY (`ownerId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `ProjectMember` ADD CONSTRAINT `ProjectMember_projectId_fkey` FOREIGN KEY (`projectId`) REFERENCES `Project`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `ProjectMember` ADD CONSTRAINT `ProjectMember_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `BoardColumn` ADD CONSTRAINT `BoardColumn_projectId_fkey` FOREIGN KEY (`projectId`) REFERENCES `Project`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Task` ADD CONSTRAINT `Task_projectId_fkey` FOREIGN KEY (`projectId`) REFERENCES `Project`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Task` ADD CONSTRAINT `Task_columnId_fkey` FOREIGN KEY (`columnId`) REFERENCES `BoardColumn`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Task` ADD CONSTRAINT `Task_parentId_fkey` FOREIGN KEY (`parentId`) REFERENCES `Task`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Task` ADD CONSTRAINT `Task_assigneeId_fkey` FOREIGN KEY (`assigneeId`) REFERENCES `User`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Task` ADD CONSTRAINT `Task_creatorId_fkey` FOREIGN KEY (`creatorId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `TaskMember` ADD CONSTRAINT `TaskMember_taskId_fkey` FOREIGN KEY (`taskId`) REFERENCES `Task`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `TaskMember` ADD CONSTRAINT `TaskMember_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `TaskTag` ADD CONSTRAINT `TaskTag_taskId_fkey` FOREIGN KEY (`taskId`) REFERENCES `Task`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -25,6 +25,13 @@ model User {
|
||||
checkIns CheckIn[]
|
||||
checkLists CheckList[]
|
||||
templates CheckListTemplate[]
|
||||
|
||||
// V0.2 任务模块
|
||||
ownedProjects Project[] @relation("ProjectOwner")
|
||||
projectMembers ProjectMember[]
|
||||
assignedTasks Task[] @relation("TaskAssignee")
|
||||
createdTasks Task[] @relation("TaskCreator")
|
||||
taskMembers TaskMember[]
|
||||
}
|
||||
|
||||
model Article {
|
||||
@@ -373,5 +380,109 @@ model CheckListTemplate {
|
||||
|
||||
@@index([userId, builtin])
|
||||
}
|
||||
|
||||
// ================== 任务模块 (V0.2 任务模块需求文档 T-*) ==================
|
||||
// 口径见 docs/V0.2/任务模块/任务模块数据库设计文档.md
|
||||
// 项目 = 隔离 + 权限单位; 看板列是实体(可增删改排序);
|
||||
// 任务状态(todo/doing/done) 与 看板列 是两个正交维度, 由列的 boundStatus 决定是否联动.
|
||||
|
||||
model Project {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @db.VarChar(50)
|
||||
ownerId Int
|
||||
owner User @relation("ProjectOwner", fields: [ownerId], references: [id], onDelete: Cascade)
|
||||
isPersonal Boolean @default(false)
|
||||
archived Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
members ProjectMember[]
|
||||
columns BoardColumn[]
|
||||
tasks Task[]
|
||||
|
||||
@@index([ownerId])
|
||||
@@index([archived])
|
||||
}
|
||||
|
||||
model ProjectMember {
|
||||
id Int @id @default(autoincrement())
|
||||
projectId Int
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
userId Int
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
permission String @default("member")
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([projectId, userId])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model BoardColumn {
|
||||
id Int @id @default(autoincrement())
|
||||
projectId Int
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
name String @db.VarChar(20)
|
||||
sortOrder Int @default(0)
|
||||
boundStatus String? @db.VarChar(8)
|
||||
retention String @default("forever") @db.VarChar(8)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
tasks Task[]
|
||||
|
||||
@@unique([projectId, name])
|
||||
@@index([projectId, sortOrder])
|
||||
}
|
||||
|
||||
model Task {
|
||||
id Int @id @default(autoincrement())
|
||||
projectId Int
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
columnId Int
|
||||
column BoardColumn @relation(fields: [columnId], references: [id], onDelete: Restrict)
|
||||
parentId Int?
|
||||
parent Task? @relation("Subtask", fields: [parentId], references: [id], onDelete: Cascade)
|
||||
children Task[] @relation("Subtask")
|
||||
|
||||
title String @db.VarChar(200)
|
||||
description String @default("") @db.Text
|
||||
status String @default("todo") @db.VarChar(8)
|
||||
doneAt DateTime?
|
||||
|
||||
assigneeId Int?
|
||||
assignee User? @relation("TaskAssignee", fields: [assigneeId], references: [id], onDelete: SetNull)
|
||||
creatorId Int
|
||||
creator User @relation("TaskCreator", fields: [creatorId], references: [id], onDelete: Cascade)
|
||||
|
||||
extras Json @default("[]")
|
||||
sortOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
members TaskMember[]
|
||||
tags TaskTag[]
|
||||
|
||||
@@index([projectId, columnId])
|
||||
@@index([projectId, status])
|
||||
@@index([parentId])
|
||||
@@index([projectId, doneAt])
|
||||
}
|
||||
|
||||
model TaskMember {
|
||||
id Int @id @default(autoincrement())
|
||||
taskId Int
|
||||
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
||||
userId Int
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([taskId, userId])
|
||||
}
|
||||
|
||||
model TaskTag {
|
||||
id Int @id @default(autoincrement())
|
||||
taskId Int
|
||||
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
||||
name String @db.VarChar(20)
|
||||
|
||||
@@index([taskId])
|
||||
}
|
||||
Reference in New Issue
Block a user