feat: 新增目标子任务管理功能模块

- 实现目标子任务的完整功能,包括数据库表设计、API接口、业务逻辑和文档说明。
- 支持用户创建、管理和跟踪目标子任务,提供增删改查操作及任务完成记录功能。
- 引入惰性任务生成机制,优化任务管理体验,提升系统性能和用户交互。
This commit is contained in:
richarjiang
2025-08-22 16:01:12 +08:00
parent 062a78a839
commit 3530d123fc
10 changed files with 1593 additions and 21 deletions

View File

@@ -1,17 +1,30 @@
import { Injectable, Logger, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { Op, WhereOptions, Order } from 'sequelize';
import { Goal, GoalStatus, GoalRepeatType } from './models/goal.model';
import { GoalCompletion } from './models/goal-completion.model';
import { GoalTask } from './models/goal-task.model';
import { CreateGoalDto } from './dto/create-goal.dto';
import { UpdateGoalDto } from './dto/update-goal.dto';
import { GoalQueryDto } from './dto/goal-query.dto';
import { CreateGoalCompletionDto } from './dto/goal-completion.dto';
import { GoalTaskService } from './services/goal-task.service';
import * as dayjs from 'dayjs';
@Injectable()
export class GoalsService {
private readonly logger = new Logger(GoalsService.name);
constructor(
@InjectModel(Goal)
private readonly goalModel: typeof Goal,
@InjectModel(GoalCompletion)
private readonly goalCompletionModel: typeof GoalCompletion,
@InjectModel(GoalTask)
private readonly goalTaskModel: typeof GoalTask,
private readonly goalTaskService: GoalTaskService,
) { }
/**
* 创建目标
*/
@@ -27,13 +40,13 @@ export class GoalsService {
throw new BadRequestException('结束日期不能早于开始日期');
}
const goal = await Goal.create({
const goal = await this.goalModel.create({
userId,
...createGoalDto,
startDate: createGoalDto.startDate ? new Date(createGoalDto.startDate) : null,
endDate: createGoalDto.endDate ? new Date(createGoalDto.endDate) : null,
startTime: createGoalDto.startTime ? createGoalDto.startTime : null,
endTime: createGoalDto.endTime ? createGoalDto.endTime : null,
startDate: createGoalDto.startDate ? new Date(createGoalDto.startDate) : undefined,
endDate: createGoalDto.endDate ? new Date(createGoalDto.endDate) : undefined,
startTime: createGoalDto.startTime ? createGoalDto.startTime : undefined,
endTime: createGoalDto.endTime ? createGoalDto.endTime : undefined,
});
this.logger.log(`用户 ${userId} 创建了目标: ${goal.title}`);
@@ -49,6 +62,9 @@ export class GoalsService {
*/
async getGoals(userId: string, query: GoalQueryDto) {
try {
// 惰性生成任务
await this.goalTaskService.generateTasksLazily(userId);
const { page = 1, pageSize = 20, status, repeatType, category, startDate, endDate, sortBy = 'createdAt', sortOrder = 'desc' } = query;
const offset = (page - 1) * pageSize;
@@ -85,7 +101,7 @@ export class GoalsService {
// 构建排序条件
const order: Order = [[sortBy, sortOrder.toUpperCase()]];
const { rows: goals, count } = await Goal.findAndCountAll({
const { rows: goals, count } = await this.goalModel.findAndCountAll({
where,
order,
offset,
@@ -97,6 +113,14 @@ export class GoalsService {
where: { deleted: false },
required: false,
},
{
model: GoalTask,
as: 'tasks',
where: { deleted: false },
required: false,
limit: 5, // 只显示最近5个任务
order: [['startDate', 'DESC']],
},
],
});
@@ -117,7 +141,10 @@ export class GoalsService {
*/
async getGoal(userId: string, goalId: string): Promise<Goal> {
try {
const goal = await Goal.findOne({
// 惰性生成任务
await this.goalTaskService.generateTasksLazily(userId, goalId);
const goal = await this.goalModel.findOne({
where: { id: goalId, userId, deleted: false },
include: [
{
@@ -128,6 +155,14 @@ export class GoalsService {
order: [['completedAt', 'DESC']],
limit: 10, // 只显示最近10次完成记录
},
{
model: GoalTask,
as: 'tasks',
where: { deleted: false },
required: false,
order: [['startDate', 'ASC']],
limit: 20, // 显示最近20个任务
},
],
});
@@ -147,7 +182,7 @@ export class GoalsService {
*/
async updateGoal(userId: string, goalId: string, updateGoalDto: UpdateGoalDto): Promise<Goal> {
try {
const goal = await Goal.findOne({
const goal = await this.goalModel.findOne({
where: { id: goalId, userId, deleted: false },
});
@@ -186,7 +221,7 @@ export class GoalsService {
*/
async deleteGoal(userId: string, goalId: string): Promise<boolean> {
try {
const goal = await Goal.findOne({
const goal = await this.goalModel.findOne({
where: { id: goalId, userId, deleted: false },
});
@@ -198,7 +233,7 @@ export class GoalsService {
await goal.update({ deleted: true });
// 软删除相关的完成记录
await GoalCompletion.update(
await this.goalCompletionModel.update(
{ deleted: true },
{ where: { goalId, userId } }
);
@@ -216,7 +251,7 @@ export class GoalsService {
*/
async completeGoal(userId: string, createCompletionDto: CreateGoalCompletionDto): Promise<GoalCompletion> {
try {
const goal = await Goal.findOne({
const goal = await this.goalModel.findOne({
where: { id: createCompletionDto.goalId, userId, deleted: false },
});
@@ -232,7 +267,7 @@ export class GoalsService {
const completedAt = createCompletionDto.completedAt ? new Date(createCompletionDto.completedAt) : new Date();
// 创建完成记录
const completion = await GoalCompletion.create({
const completion = await this.goalCompletionModel.create({
goalId: createCompletionDto.goalId,
userId,
completedAt,
@@ -266,7 +301,7 @@ export class GoalsService {
const offset = (page - 1) * pageSize;
// 验证目标存在
const goal = await Goal.findOne({
const goal = await this.goalModel.findOne({
where: { id: goalId, userId, deleted: false },
});
@@ -291,7 +326,7 @@ export class GoalsService {
}
}
const { rows: completions, count } = await GoalCompletion.findAndCountAll({
const { rows: completions, count } = await this.goalCompletionModel.findAndCountAll({
where,
order: [['completedAt', 'DESC']],
offset,
@@ -322,7 +357,7 @@ export class GoalsService {
*/
async getGoalStats(userId: string) {
try {
const goals = await Goal.findAll({
const goals = await this.goalModel.findAll({
where: { userId, deleted: false },
include: [
{