feat(medications): 新增药物激活状态管理及相关记录更新功能
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
|||||||
IsArray,
|
IsArray,
|
||||||
IsDateString,
|
IsDateString,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
|
IsBoolean,
|
||||||
Min,
|
Min,
|
||||||
ArrayMinSize,
|
ArrayMinSize,
|
||||||
Matches,
|
Matches,
|
||||||
@@ -101,4 +102,14 @@ export class CreateMedicationDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
note?: string;
|
note?: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '是否激活',
|
||||||
|
example: true,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
isActive?: boolean;
|
||||||
}
|
}
|
||||||
@@ -88,9 +88,14 @@ export class MedicationsController {
|
|||||||
updateDto,
|
updateDto,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 如果更新了服药时间,重新设置提醒
|
// 如果更新了服药时间或isActive字段,重新设置提醒
|
||||||
if (updateDto.medicationTimes) {
|
if (updateDto.medicationTimes || updateDto.isActive !== undefined) {
|
||||||
await this.reminderService.setupRemindersForMedication(medication);
|
if (medication.isActive) {
|
||||||
|
await this.reminderService.setupRemindersForMedication(medication);
|
||||||
|
} else {
|
||||||
|
// 如果停用药物,取消提醒
|
||||||
|
await this.reminderService.cancelRemindersForMedication(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApiResponseDto.success(medication, '更新成功');
|
return ApiResponseDto.success(medication, '更新成功');
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { InjectModel } from '@nestjs/sequelize';
|
import { InjectModel } from '@nestjs/sequelize';
|
||||||
import { Medication } from './models/medication.model';
|
import { Medication } from './models/medication.model';
|
||||||
|
import { MedicationRecord } from './models/medication-record.model';
|
||||||
import { CreateMedicationDto } from './dto/create-medication.dto';
|
import { CreateMedicationDto } from './dto/create-medication.dto';
|
||||||
import { UpdateMedicationDto } from './dto/update-medication.dto';
|
import { UpdateMedicationDto } from './dto/update-medication.dto';
|
||||||
|
import { MedicationStatusEnum } from './enums/medication-status.enum';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { Op } from 'sequelize';
|
||||||
|
import * as dayjs from 'dayjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 药物管理服务
|
* 药物管理服务
|
||||||
@@ -20,6 +24,8 @@ export class MedicationsService {
|
|||||||
constructor(
|
constructor(
|
||||||
@InjectModel(Medication)
|
@InjectModel(Medication)
|
||||||
private readonly medicationModel: typeof 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),
|
startDate: new Date(createDto.startDate),
|
||||||
endDate: createDto.endDate ? new Date(createDto.endDate) : null,
|
endDate: createDto.endDate ? new Date(createDto.endDate) : null,
|
||||||
note: createDto.note,
|
note: createDto.note,
|
||||||
isActive: true,
|
isActive: createDto.isActive !== undefined ? createDto.isActive : true,
|
||||||
deleted: false,
|
deleted: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -114,6 +120,9 @@ export class MedicationsService {
|
|||||||
): Promise<Medication> {
|
): Promise<Medication> {
|
||||||
const medication = await this.findOne(id, userId);
|
const medication = await this.findOne(id, userId);
|
||||||
|
|
||||||
|
// 保存更新前的状态
|
||||||
|
const wasActive = medication.isActive;
|
||||||
|
|
||||||
// 更新字段
|
// 更新字段
|
||||||
if (updateDto.name !== undefined) {
|
if (updateDto.name !== undefined) {
|
||||||
medication.name = updateDto.name;
|
medication.name = updateDto.name;
|
||||||
@@ -148,9 +157,22 @@ export class MedicationsService {
|
|||||||
if (updateDto.note !== undefined) {
|
if (updateDto.note !== undefined) {
|
||||||
medication.note = updateDto.note;
|
medication.note = updateDto.note;
|
||||||
}
|
}
|
||||||
|
if (updateDto.isActive !== undefined) {
|
||||||
|
medication.isActive = updateDto.isActive;
|
||||||
|
}
|
||||||
|
|
||||||
await medication.save();
|
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}`);
|
this.logger.log(`成功更新药物 ${id}`);
|
||||||
return medication;
|
return medication;
|
||||||
}
|
}
|
||||||
@@ -173,9 +195,21 @@ export class MedicationsService {
|
|||||||
async deactivate(id: string, userId: string): Promise<Medication> {
|
async deactivate(id: string, userId: string): Promise<Medication> {
|
||||||
const medication = await this.findOne(id, userId);
|
const medication = await this.findOne(id, userId);
|
||||||
|
|
||||||
|
// 如果已经是停用状态,不需要处理
|
||||||
|
if (!medication.isActive) {
|
||||||
|
this.logger.log(`药物 ${id} 已经是停用状态`);
|
||||||
|
return medication;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasActive = medication.isActive;
|
||||||
medication.isActive = false;
|
medication.isActive = false;
|
||||||
await medication.save();
|
await medication.save();
|
||||||
|
|
||||||
|
// 删除当天未服用的记录
|
||||||
|
if (wasActive && !medication.isActive) {
|
||||||
|
await this.deleteTodayUntakenRecords(medication);
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.log(`成功停用药物 ${id}`);
|
this.logger.log(`成功停用药物 ${id}`);
|
||||||
return medication;
|
return medication;
|
||||||
}
|
}
|
||||||
@@ -186,10 +220,139 @@ export class MedicationsService {
|
|||||||
async activate(id: string, userId: string): Promise<Medication> {
|
async activate(id: string, userId: string): Promise<Medication> {
|
||||||
const medication = await this.findOne(id, userId);
|
const medication = await this.findOne(id, userId);
|
||||||
|
|
||||||
|
// 如果已经是激活状态,不需要处理
|
||||||
|
if (medication.isActive) {
|
||||||
|
this.logger.log(`药物 ${id} 已经是激活状态`);
|
||||||
|
return medication;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasActive = medication.isActive;
|
||||||
medication.isActive = true;
|
medication.isActive = true;
|
||||||
await medication.save();
|
await medication.save();
|
||||||
|
|
||||||
|
// 当药物从停用变为激活时,RecordGeneratorService 会负责生成新记录
|
||||||
|
// 但这里不需要手动处理,因为采用的是惰性生成策略
|
||||||
|
|
||||||
this.logger.log(`成功激活药物 ${id}`);
|
this.logger.log(`成功激活药物 ${id}`);
|
||||||
return medication;
|
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 now = new Date();
|
||||||
const status =
|
const medicationStartDate = dayjs(medication.startDate).startOf('day');
|
||||||
scheduledTime <= now
|
|
||||||
|
// 如果药物开始日期是今天,无论当前时间如何,都设置为 UPCOMING
|
||||||
|
const status = targetDate.isSame(medicationStartDate, 'day')
|
||||||
|
? MedicationStatusEnum.UPCOMING
|
||||||
|
: scheduledTime <= now
|
||||||
? MedicationStatusEnum.MISSED
|
? MedicationStatusEnum.MISSED
|
||||||
: MedicationStatusEnum.UPCOMING;
|
: MedicationStatusEnum.UPCOMING;
|
||||||
|
|
||||||
@@ -187,29 +191,28 @@ export class RecordGeneratorService {
|
|||||||
*/
|
*/
|
||||||
async ensureRecordsExist(userId: string, date: string): Promise<boolean> {
|
async ensureRecordsExist(userId: string, date: string): Promise<boolean> {
|
||||||
const targetDate = dayjs(date).format('YYYY-MM-DD');
|
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 startOfDay = dayjs(date).startOf('day').toDate();
|
||||||
const endOfDay = dayjs(date).endOf('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 Op = require('sequelize').Op;
|
||||||
const recordsOnDate = await this.recordModel.count({
|
const existingRecords = await this.recordModel.findAll({
|
||||||
where: {
|
where: {
|
||||||
userId,
|
userId,
|
||||||
deleted: false,
|
deleted: false,
|
||||||
@@ -217,10 +220,37 @@ export class RecordGeneratorService {
|
|||||||
[Op.between]: [startOfDay, endOfDay],
|
[Op.between]: [startOfDay, endOfDay],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
attributes: ['medicationId'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (recordsOnDate === 0) {
|
// 3. 获取已有记录的药物ID集合
|
||||||
await this.generateRecordsForDate(userId, targetDate);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user