import { Injectable, Logger } from '@nestjs/common'; import { InjectModel } from '@nestjs/sequelize'; import { Medication } from '../models/medication.model'; import { MedicationRecord } from '../models/medication-record.model'; import { MedicationStatusEnum } from '../enums/medication-status.enum'; import { RepeatPatternEnum } from '../enums/repeat-pattern.enum'; import { v4 as uuidv4 } from 'uuid'; import * as dayjs from 'dayjs'; import * as utc from 'dayjs/plugin/utc'; import * as timezone from 'dayjs/plugin/timezone'; dayjs.extend(utc); dayjs.extend(timezone); /** * 服药记录生成服务 * 实现惰性生成策略:当查询时检查并生成当天记录 */ @Injectable() export class RecordGeneratorService { private readonly logger = new Logger(RecordGeneratorService.name); constructor( @InjectModel(Medication) private readonly medicationModel: typeof Medication, @InjectModel(MedicationRecord) private readonly recordModel: typeof MedicationRecord, ) {} /** * 为指定日期生成服药记录 * @param userId 用户ID * @param date 日期字符串(YYYY-MM-DD) */ async generateRecordsForDate(userId: string, date: string): Promise { this.logger.log(`开始为用户 ${userId} 生成 ${date} 的服药记录`); // 解析目标日期 const targetDate = dayjs(date).startOf('day'); // 查询用户所有激活的药物 const medications = await this.medicationModel.findAll({ where: { userId, isActive: true, deleted: false, }, }); if (medications.length === 0) { this.logger.log(`用户 ${userId} 没有激活的药物`); return; } // 为每个药物生成当天的服药记录 for (const medication of medications) { await this.generateRecordsForMedicationOnDate(medication, targetDate); } this.logger.log(`成功为用户 ${userId} 生成 ${date} 的服药记录`); } /** * 为单个药物在指定日期生成服药记录 */ private async generateRecordsForMedicationOnDate( medication: Medication, targetDate: dayjs.Dayjs, ): Promise { // 检查该日期是否在药物的有效期内 if (!this.isDateInMedicationRange(medication, targetDate)) { this.logger.debug( `药物 ${medication.id} 在 ${targetDate.format('YYYY-MM-DD')} 不在有效期内`, ); return; } // 检查是否已经生成过该日期的记录 const existingRecords = await this.recordModel.findAll({ where: { medicationId: medication.id, userId: medication.userId, deleted: false, }, }); // 过滤出当天的记录 const recordsOnDate = existingRecords.filter((record) => { const recordDate = dayjs(record.scheduledTime).startOf('day'); return recordDate.isSame(targetDate, 'day'); }); if (recordsOnDate.length > 0) { this.logger.debug( `药物 ${medication.id} 在 ${targetDate.format('YYYY-MM-DD')} 的记录已存在`, ); return; } // 根据重复模式生成记录 if (medication.repeatPattern === RepeatPatternEnum.DAILY) { await this.generateDailyRecords(medication, targetDate); } // 未来可以扩展 WEEKLY 和 CUSTOM 模式 } /** * 生成每日重复模式的记录 */ private async generateDailyRecords( medication: Medication, targetDate: dayjs.Dayjs, ): Promise { const records: any[] = []; // 为每个服药时间生成一条记录 for (const timeStr of medication.medicationTimes) { // 解析时间字符串(HH:mm) const [hours, minutes] = timeStr.split(':').map(Number); // 创建计划服药时间(UTC) const scheduledTime = targetDate .hour(hours) .minute(minutes) .second(0) .millisecond(0) .toDate(); // 判断初始状态 const now = new Date(); const status = scheduledTime <= now ? MedicationStatusEnum.MISSED : MedicationStatusEnum.UPCOMING; records.push({ id: uuidv4(), medicationId: medication.id, userId: medication.userId, scheduledTime, actualTime: null, status, note: null, deleted: false, }); } // 批量创建记录 if (records.length > 0) { await this.recordModel.bulkCreate(records); this.logger.log( `为药物 ${medication.id} 在 ${targetDate.format('YYYY-MM-DD')} 生成了 ${records.length} 条记录`, ); } } /** * 检查日期是否在药物有效期内 */ private isDateInMedicationRange( medication: Medication, targetDate: dayjs.Dayjs, ): boolean { const startDate = dayjs(medication.startDate).startOf('day'); const endDate = medication.endDate ? dayjs(medication.endDate).startOf('day') : null; // 检查是否在开始日期之后 if (targetDate.isBefore(startDate, 'day')) { return false; } // 检查是否在结束日期之前(如果有结束日期) if (endDate && targetDate.isAfter(endDate, 'day')) { return false; } return true; } /** * 检查并生成指定日期的记录(如果不存在) * @param userId 用户ID * @param date 日期字符串(YYYY-MM-DD) * @returns 是否生成了新记录 */ async ensureRecordsExist(userId: string, date: string): Promise { const targetDate = dayjs(date).format('YYYY-MM-DD'); // 检查该日期是否已有记录 const startOfDay = dayjs(date).startOf('day').toDate(); const endOfDay = dayjs(date).endOf('day').toDate(); const existingRecords = await this.recordModel.count({ where: { userId, deleted: false, }, }); // 简单判断:如果没有任何记录,则生成 const recordsCount = await this.recordModel.count({ where: { userId, deleted: false, }, }); // 这里使用更精确的查询 const Op = require('sequelize').Op; const recordsOnDate = await this.recordModel.count({ where: { userId, deleted: false, scheduledTime: { [Op.between]: [startOfDay, endOfDay], }, }, }); if (recordsOnDate === 0) { await this.generateRecordsForDate(userId, targetDate); return true; } return false; } }