feat(medications): 新增药物激活状态管理及相关记录更新功能
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
IsArray,
|
||||
IsDateString,
|
||||
IsOptional,
|
||||
IsBoolean,
|
||||
Min,
|
||||
ArrayMinSize,
|
||||
Matches,
|
||||
@@ -101,4 +102,14 @@ export class CreateMedicationDto {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
note?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '是否激活',
|
||||
example: true,
|
||||
required: false,
|
||||
default: true
|
||||
})
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
}
|
||||
@@ -88,9 +88,14 @@ export class MedicationsController {
|
||||
updateDto,
|
||||
);
|
||||
|
||||
// 如果更新了服药时间,重新设置提醒
|
||||
if (updateDto.medicationTimes) {
|
||||
await this.reminderService.setupRemindersForMedication(medication);
|
||||
// 如果更新了服药时间或isActive字段,重新设置提醒
|
||||
if (updateDto.medicationTimes || updateDto.isActive !== undefined) {
|
||||
if (medication.isActive) {
|
||||
await this.reminderService.setupRemindersForMedication(medication);
|
||||
} else {
|
||||
// 如果停用药物,取消提醒
|
||||
await this.reminderService.cancelRemindersForMedication(id);
|
||||
}
|
||||
}
|
||||
|
||||
return ApiResponseDto.success(medication, '更新成功');
|
||||
|
||||
@@ -6,9 +6,13 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/sequelize';
|
||||
import { Medication } from './models/medication.model';
|
||||
import { MedicationRecord } from './models/medication-record.model';
|
||||
import { CreateMedicationDto } from './dto/create-medication.dto';
|
||||
import { UpdateMedicationDto } from './dto/update-medication.dto';
|
||||
import { MedicationStatusEnum } from './enums/medication-status.enum';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Op } from 'sequelize';
|
||||
import * as dayjs from 'dayjs';
|
||||
|
||||
/**
|
||||
* 药物管理服务
|
||||
@@ -20,6 +24,8 @@ export class MedicationsService {
|
||||
constructor(
|
||||
@InjectModel(Medication)
|
||||
private readonly medicationModel: typeof Medication,
|
||||
@InjectModel(MedicationRecord)
|
||||
private readonly recordModel: typeof MedicationRecord,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -45,7 +51,7 @@ export class MedicationsService {
|
||||
startDate: new Date(createDto.startDate),
|
||||
endDate: createDto.endDate ? new Date(createDto.endDate) : null,
|
||||
note: createDto.note,
|
||||
isActive: true,
|
||||
isActive: createDto.isActive !== undefined ? createDto.isActive : true,
|
||||
deleted: false,
|
||||
});
|
||||
|
||||
@@ -114,6 +120,9 @@ export class MedicationsService {
|
||||
): Promise<Medication> {
|
||||
const medication = await this.findOne(id, userId);
|
||||
|
||||
// 保存更新前的状态
|
||||
const wasActive = medication.isActive;
|
||||
|
||||
// 更新字段
|
||||
if (updateDto.name !== undefined) {
|
||||
medication.name = updateDto.name;
|
||||
@@ -148,9 +157,22 @@ export class MedicationsService {
|
||||
if (updateDto.note !== undefined) {
|
||||
medication.note = updateDto.note;
|
||||
}
|
||||
if (updateDto.isActive !== undefined) {
|
||||
medication.isActive = updateDto.isActive;
|
||||
}
|
||||
|
||||
await medication.save();
|
||||
|
||||
// 如果从激活状态变为停用状态,删除当天未服用的记录
|
||||
if (updateDto.isActive !== undefined && wasActive && !updateDto.isActive) {
|
||||
await this.deleteTodayUntakenRecords(medication);
|
||||
}
|
||||
|
||||
// 如果更新了服药时间,更新当天相关的未服用记录
|
||||
if (updateDto.medicationTimes) {
|
||||
await this.updateTodayUntakenRecords(medication, updateDto.medicationTimes);
|
||||
}
|
||||
|
||||
this.logger.log(`成功更新药物 ${id}`);
|
||||
return medication;
|
||||
}
|
||||
@@ -173,9 +195,21 @@ export class MedicationsService {
|
||||
async deactivate(id: string, userId: string): Promise<Medication> {
|
||||
const medication = await this.findOne(id, userId);
|
||||
|
||||
// 如果已经是停用状态,不需要处理
|
||||
if (!medication.isActive) {
|
||||
this.logger.log(`药物 ${id} 已经是停用状态`);
|
||||
return medication;
|
||||
}
|
||||
|
||||
const wasActive = medication.isActive;
|
||||
medication.isActive = false;
|
||||
await medication.save();
|
||||
|
||||
// 删除当天未服用的记录
|
||||
if (wasActive && !medication.isActive) {
|
||||
await this.deleteTodayUntakenRecords(medication);
|
||||
}
|
||||
|
||||
this.logger.log(`成功停用药物 ${id}`);
|
||||
return medication;
|
||||
}
|
||||
@@ -186,10 +220,139 @@ export class MedicationsService {
|
||||
async activate(id: string, userId: string): Promise<Medication> {
|
||||
const medication = await this.findOne(id, userId);
|
||||
|
||||
// 如果已经是激活状态,不需要处理
|
||||
if (medication.isActive) {
|
||||
this.logger.log(`药物 ${id} 已经是激活状态`);
|
||||
return medication;
|
||||
}
|
||||
|
||||
const wasActive = medication.isActive;
|
||||
medication.isActive = true;
|
||||
await medication.save();
|
||||
|
||||
// 当药物从停用变为激活时,RecordGeneratorService 会负责生成新记录
|
||||
// 但这里不需要手动处理,因为采用的是惰性生成策略
|
||||
|
||||
this.logger.log(`成功激活药物 ${id}`);
|
||||
return medication;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新当天相关的未服用记录
|
||||
* 当药物的服药时间被更新时,将当天未服用的记录更新为新的服药时间
|
||||
*/
|
||||
private async updateTodayUntakenRecords(
|
||||
medication: Medication,
|
||||
newMedicationTimes: string[],
|
||||
): Promise<void> {
|
||||
const today = dayjs().format('YYYY-MM-DD');
|
||||
const todayStart = dayjs(today).startOf('day').toDate();
|
||||
const todayEnd = dayjs(today).endOf('day').toDate();
|
||||
|
||||
this.logger.log(
|
||||
`开始更新药物 ${medication.id} 在 ${today} 的未服用记录`,
|
||||
);
|
||||
|
||||
// 查询当天该药物的未服用记录(状态为 UPCOMING)
|
||||
const recordsToUpdate = await this.recordModel.findAll({
|
||||
where: {
|
||||
medicationId: medication.id,
|
||||
userId: medication.userId,
|
||||
status: MedicationStatusEnum.UPCOMING,
|
||||
scheduledTime: {
|
||||
[Op.between]: [todayStart, todayEnd],
|
||||
},
|
||||
deleted: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (recordsToUpdate.length === 0) {
|
||||
this.logger.log(`没有找到 ${medication.id} 需要更新的记录`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 排序记录,按计划时间升序
|
||||
recordsToUpdate.sort((a, b) => {
|
||||
const timeA = dayjs(a.scheduledTime).format('HH:mm');
|
||||
const timeB = dayjs(b.scheduledTime).format('HH:mm');
|
||||
return timeA.localeCompare(timeB);
|
||||
});
|
||||
|
||||
// 更新记录的计划时间
|
||||
for (let i = 0; i < recordsToUpdate.length && i < newMedicationTimes.length; i++) {
|
||||
const record = recordsToUpdate[i];
|
||||
const newTime = newMedicationTimes[i];
|
||||
|
||||
// 解析新的时间字符串(HH:mm)
|
||||
const [hours, minutes] = newTime.split(':').map(Number);
|
||||
|
||||
// 重新计算计划服药时间(基于今天)
|
||||
const newScheduledTime = dayjs(today)
|
||||
.hour(hours)
|
||||
.minute(minutes)
|
||||
.second(0)
|
||||
.millisecond(0)
|
||||
.toDate();
|
||||
|
||||
record.scheduledTime = newScheduledTime;
|
||||
await record.save();
|
||||
|
||||
this.logger.log(
|
||||
`更新记录 ${record.id} 的时间从 ${dayjs(record.scheduledTime).format('HH:mm')} 到 ${newTime}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`成功更新了 ${recordsToUpdate.length} 条 ${medication.id} 的当天记录`,
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 删除当天未服用的药物记录
|
||||
* 当药物被停用时,删除当天生成的但还未服用的记录
|
||||
*/
|
||||
private async deleteTodayUntakenRecords(medication: Medication): Promise<void> {
|
||||
// 使用当前时间作为基准,查询当前时间及未来的未服用记录
|
||||
const now = new Date();
|
||||
|
||||
this.logger.log(
|
||||
`开始删除药物 ${medication.id} 从现在起(${dayjs(now).format('YYYY-MM-DD HH:mm')})的未服用记录`,
|
||||
);
|
||||
|
||||
// 查询从现在开始的所有未服用记录(状态为 UPCOMING)
|
||||
const recordsToDelete = await this.recordModel.findAll({
|
||||
where: {
|
||||
medicationId: medication.id,
|
||||
userId: medication.userId,
|
||||
status: MedicationStatusEnum.UPCOMING,
|
||||
scheduledTime: {
|
||||
[Op.gte]: now, // 大于等于当前时间
|
||||
},
|
||||
deleted: false,
|
||||
},
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`找到 ${recordsToDelete.length} 条需要删除的 ${medication.id} 记录`,
|
||||
);
|
||||
|
||||
if (recordsToDelete.length === 0) {
|
||||
this.logger.log(`没有找到 ${medication.id} 需要删除的记录`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 软删除记录
|
||||
for (const record of recordsToDelete) {
|
||||
const scheduledTime = dayjs(record.scheduledTime).format('YYYY-MM-DD HH:mm');
|
||||
record.deleted = true;
|
||||
await record.save();
|
||||
|
||||
this.logger.log(
|
||||
`软删除记录 ${record.id},计划时间:${scheduledTime}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`成功删除了 ${recordsToDelete.length} 条 ${medication.id} 的未服用记录`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -128,8 +128,12 @@ export class RecordGeneratorService {
|
||||
|
||||
// 判断初始状态
|
||||
const now = new Date();
|
||||
const status =
|
||||
scheduledTime <= now
|
||||
const medicationStartDate = dayjs(medication.startDate).startOf('day');
|
||||
|
||||
// 如果药物开始日期是今天,无论当前时间如何,都设置为 UPCOMING
|
||||
const status = targetDate.isSame(medicationStartDate, 'day')
|
||||
? MedicationStatusEnum.UPCOMING
|
||||
: scheduledTime <= now
|
||||
? MedicationStatusEnum.MISSED
|
||||
: MedicationStatusEnum.UPCOMING;
|
||||
|
||||
@@ -187,29 +191,28 @@ export class RecordGeneratorService {
|
||||
*/
|
||||
async ensureRecordsExist(userId: string, date: string): Promise<boolean> {
|
||||
const targetDate = dayjs(date).format('YYYY-MM-DD');
|
||||
const targetDateDayjs = dayjs(date).startOf('day');
|
||||
|
||||
// 检查该日期是否已有记录
|
||||
// 1. 查询用户所有激活的药物
|
||||
const activeMedications = await this.medicationModel.findAll({
|
||||
where: {
|
||||
userId,
|
||||
isActive: true,
|
||||
deleted: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (activeMedications.length === 0) {
|
||||
this.logger.debug(`用户 ${userId} 没有激活的药物`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查该日期是否已有记录
|
||||
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({
|
||||
const existingRecords = await this.recordModel.findAll({
|
||||
where: {
|
||||
userId,
|
||||
deleted: false,
|
||||
@@ -217,10 +220,37 @@ export class RecordGeneratorService {
|
||||
[Op.between]: [startOfDay, endOfDay],
|
||||
},
|
||||
},
|
||||
attributes: ['medicationId'],
|
||||
});
|
||||
|
||||
if (recordsOnDate === 0) {
|
||||
await this.generateRecordsForDate(userId, targetDate);
|
||||
// 3. 获取已有记录的药物ID集合
|
||||
const existingMedicationIds = new Set(
|
||||
existingRecords.map((record) => record.medicationId),
|
||||
);
|
||||
|
||||
// 4. 找出需要生成记录的药物(在有效期内且未生成记录的)
|
||||
const medicationsNeedRecords = activeMedications.filter((medication) => {
|
||||
// 检查是否在有效期内
|
||||
if (!this.isDateInMedicationRange(medication, targetDateDayjs)) {
|
||||
return false;
|
||||
}
|
||||
// 检查是否已生成记录
|
||||
return !existingMedicationIds.has(medication.id);
|
||||
});
|
||||
|
||||
// 5. 为需要的药物生成记录
|
||||
if (medicationsNeedRecords.length > 0) {
|
||||
this.logger.log(
|
||||
`为用户 ${userId} 在 ${targetDate} 生成 ${medicationsNeedRecords.length} 个药物的记录`,
|
||||
);
|
||||
|
||||
for (const medication of medicationsNeedRecords) {
|
||||
await this.generateRecordsForMedicationOnDate(
|
||||
medication,
|
||||
targetDateDayjs,
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user