import { Injectable, Logger, NotFoundException, BadRequestException } from '@nestjs/common'; import { InjectModel, InjectConnection } from '@nestjs/sequelize'; import { Op, WhereOptions, Order, Transaction } from 'sequelize'; import { Sequelize } from 'sequelize-typescript'; 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, @InjectConnection() private readonly sequelize: Sequelize, private readonly goalTaskService: GoalTaskService, ) { } /** * 创建目标 */ async createGoal(userId: string, createGoalDto: CreateGoalDto): Promise { try { this.logger.log(`createGoal: ${JSON.stringify(createGoalDto, null, 2)}`); // 验证自定义重复规则 if (createGoalDto.repeatType === GoalRepeatType.CUSTOM && !createGoalDto.customRepeatRule) { throw new BadRequestException('自定义重复类型必须提供自定义重复规则'); } // 验证日期逻辑 if (createGoalDto.endDate && dayjs(createGoalDto.endDate).isBefore(createGoalDto.startDate)) { throw new BadRequestException('结束日期不能早于开始日期'); } const goal = await this.goalModel.create({ userId, ...createGoalDto, 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}`); return goal; } catch (error) { this.logger.error(`创建目标失败: ${error.message}`); throw error; } } /** * 获取用户的目标列表 */ 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; // 构建查询条件 const where: WhereOptions = { userId, deleted: false, }; if (status) { where.status = status; } if (repeatType) { where.repeatType = repeatType; } if (category) { where.category = category; } if (startDate || endDate) { where.startDate = {}; if (startDate) { where.startDate[Op.gte] = new Date(startDate); } if (endDate) { where.startDate[Op.lte] = new Date(endDate); } } this.logger.log(`查询条件: ${JSON.stringify(where)}`); // 构建排序条件 const order: Order = [[sortBy, sortOrder.toUpperCase()]]; const { rows: goals, count } = await this.goalModel.findAndCountAll({ where, order, offset, limit: pageSize, include: [ { model: GoalCompletion, as: 'completions', where: { deleted: false }, required: false, }, { model: GoalTask, as: 'tasks', where: { deleted: false }, required: false, limit: 5, // 只显示最近5个任务 order: [['startDate', 'DESC']], }, ], }); return { page, pageSize, total: count, list: goals.map(goal => this.formatGoalResponse(goal)), }; } catch (error) { this.logger.error(`获取目标列表失败: ${error.message}`); throw error; } } /** * 更新目标 */ async updateGoal(userId: string, goalId: string, updateGoalDto: UpdateGoalDto): Promise { try { this.logger.log(`updateGoal updateGoalDto: ${JSON.stringify(updateGoalDto, null, 2)}`); const goal = await this.goalModel.findOne({ where: { id: goalId, userId, deleted: false }, }); if (!goal) { throw new NotFoundException('目标不存在'); } // 验证日期逻辑 if (updateGoalDto.endDate && updateGoalDto.startDate) { if (dayjs(updateGoalDto.endDate).isBefore(updateGoalDto.startDate)) { throw new BadRequestException('结束日期不能早于开始日期'); } } // 如果目标已完成,不允许修改 if (goal.status === GoalStatus.COMPLETED && updateGoalDto.status !== GoalStatus.COMPLETED) { throw new BadRequestException('已完成的目标不能修改状态'); } await goal.update({ ...updateGoalDto, endDate: updateGoalDto.endDate ? new Date(updateGoalDto.endDate) : undefined, }); this.logger.log(`用户 ${userId} 更新了目标: ${goal.title}`); return this.formatGoalResponse(goal); } catch (error) { this.logger.error(`更新目标失败: ${error.message}`); throw error; } } /** * 删除目标 */ async deleteGoal(userId: string, goalId: string): Promise { const transaction = await this.sequelize.transaction(); try { // 验证目标存在 const goal = await this.goalModel.findOne({ where: { id: goalId, userId, deleted: false }, transaction, }); if (!goal) { await transaction.rollback(); throw new NotFoundException('目标不存在'); } // 使用事务删除目标及其相关数据 await Promise.all([ // 软删除目标本身 this.goalModel.update( { deleted: true }, { where: { id: goalId, userId, deleted: false }, transaction } ), // 软删除目标完成记录 this.goalCompletionModel.update( { deleted: true }, { where: { goalId, userId, deleted: false }, transaction } ), // 软删除与目标关联的任务 this.goalTaskModel.update( { deleted: true }, { where: { goalId, userId, deleted: false }, transaction } ), ]); // 提交事务 await transaction.commit(); this.logger.log(`用户 ${userId} 删除了目标: ${goal.title}`); return true; } catch (error) { // 回滚事务 await transaction.rollback(); this.logger.error(`删除目标失败: ${error.message}`); throw error; } } /** * 记录目标完成 */ async completeGoal(userId: string, createCompletionDto: CreateGoalCompletionDto): Promise { try { const goal = await this.goalModel.findOne({ where: { id: createCompletionDto.goalId, userId, deleted: false }, }); if (!goal) { throw new NotFoundException('目标不存在'); } if (goal.status !== GoalStatus.ACTIVE) { throw new BadRequestException('只有激活状态的目标才能记录完成'); } const completionCount = createCompletionDto.completionCount || 1; const completedAt = createCompletionDto.completedAt ? new Date(createCompletionDto.completedAt) : new Date(); // 创建完成记录 const completion = await this.goalCompletionModel.create({ goalId: createCompletionDto.goalId, userId, completedAt, completionCount, notes: createCompletionDto.notes, }); // 更新目标的完成次数 const newCompletedCount = goal.completedCount + completionCount; await goal.update({ completedCount: newCompletedCount }); // 检查是否达到目标总次数 if (goal.targetCount && newCompletedCount >= goal.targetCount) { await goal.update({ status: GoalStatus.COMPLETED }); } this.logger.log(`用户 ${userId} 完成了目标: ${goal.title}`); return completion; } catch (error) { this.logger.error(`记录目标完成失败: ${error.message}`); throw error; } } /** * 获取目标完成记录 */ async getGoalCompletions(userId: string, goalId: string, query: any = {}) { try { const { page = 1, pageSize = 20, startDate, endDate } = query; const offset = (page - 1) * pageSize; // 验证目标存在 const goal = await this.goalModel.findOne({ where: { id: goalId, userId, deleted: false }, }); if (!goal) { throw new NotFoundException('目标不存在'); } // 构建查询条件 const where: WhereOptions = { goalId, userId, deleted: false, }; if (startDate || endDate) { where.completedAt = {}; if (startDate) { where.completedAt[Op.gte] = new Date(startDate); } if (endDate) { where.completedAt[Op.lte] = new Date(endDate); } } const { rows: completions, count } = await this.goalCompletionModel.findAndCountAll({ where, order: [['completedAt', 'DESC']], offset, limit: pageSize, include: [ { model: Goal, as: 'goal', attributes: ['id', 'title', 'repeatType', 'frequency'], }, ], }); return { page, pageSize, total: count, items: completions, }; } catch (error) { this.logger.error(`获取目标完成记录失败: ${error.message}`); throw error; } } /** * 获取目标统计信息 */ async getGoalStats(userId: string) { try { const goals = await this.goalModel.findAll({ where: { userId, deleted: false }, include: [ { model: GoalCompletion, as: 'completions', where: { deleted: false }, required: false, }, ], }); const stats = { total: goals.length, active: goals.filter(g => g.status === GoalStatus.ACTIVE).length, completed: goals.filter(g => g.status === GoalStatus.COMPLETED).length, paused: goals.filter(g => g.status === GoalStatus.PAUSED).length, cancelled: goals.filter(g => g.status === GoalStatus.CANCELLED).length, byCategory: {}, byRepeatType: {}, totalCompletions: 0, thisWeekCompletions: 0, thisMonthCompletions: 0, }; const now = dayjs(); const weekStart = now.startOf('week'); const monthStart = now.startOf('month'); goals.forEach(goal => { // 按分类统计 if (goal.category) { stats.byCategory[goal.category] = (stats.byCategory[goal.category] || 0) + 1; } // 按重复类型统计 stats.byRepeatType[goal.repeatType] = (stats.byRepeatType[goal.repeatType] || 0) + 1; // 统计完成次数 stats.totalCompletions += goal.completedCount; // 统计本周和本月的完成次数 goal.completions?.forEach(completion => { const completionDate = dayjs(completion.completedAt); if (completionDate.isAfter(weekStart)) { stats.thisWeekCompletions += completion.completionCount; } if (completionDate.isAfter(monthStart)) { stats.thisMonthCompletions += completion.completionCount; } }); }); return stats; } catch (error) { this.logger.error(`获取目标统计失败: ${error.message}`); throw error; } } /** * 格式化目标响应 */ private formatGoalResponse(goal: Goal) { const goalData = goal.toJSON(); // 计算进度百分比 if (goalData.targetCount) { goalData.progressPercentage = Math.min(100, Math.round((goalData.completedCount / goalData.targetCount) * 100)); } else { goalData.progressPercentage = 0; } // 计算剩余天数 if (goalData.endDate) { const endDate = dayjs(goalData.endDate); const now = dayjs(); goalData.daysRemaining = Math.max(0, endDate.diff(now, 'day')); } else { goalData.daysRemaining = null; } return goalData; } }