feat(medications): 新增完整的药物管理和服药提醒功能
实现了包含药物信息管理、服药记录追踪、统计分析、自动状态更新和推送提醒的完整药物管理系统。 核心功能: - 药物 CRUD 操作,支持多种剂型和自定义服药时间 - 惰性生成服药记录策略,查询时才生成当天记录 - 定时任务自动更新过期记录状态(每30分钟) - 服药前15分钟自动推送提醒(每5分钟检查) - 每日/范围/总体统计分析功能 - 完整的 API 文档和数据库建表脚本 技术实现: - 使用 Sequelize ORM 管理 MySQL 数据表 - 集成 @nestjs/schedule 实现定时任务 - 复用现有推送通知系统发送提醒 - 采用软删除和权限验证保障数据安全
This commit is contained in:
195
src/medications/medications.service.ts
Normal file
195
src/medications/medications.service.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
ForbiddenException,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/sequelize';
|
||||
import { Medication } from './models/medication.model';
|
||||
import { CreateMedicationDto } from './dto/create-medication.dto';
|
||||
import { UpdateMedicationDto } from './dto/update-medication.dto';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
/**
|
||||
* 药物管理服务
|
||||
*/
|
||||
@Injectable()
|
||||
export class MedicationsService {
|
||||
private readonly logger = new Logger(MedicationsService.name);
|
||||
|
||||
constructor(
|
||||
@InjectModel(Medication)
|
||||
private readonly medicationModel: typeof Medication,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 创建药物
|
||||
*/
|
||||
async create(
|
||||
userId: string,
|
||||
createDto: CreateMedicationDto,
|
||||
): Promise<Medication> {
|
||||
this.logger.log(`用户 ${userId} 创建药物:${createDto.name}`);
|
||||
|
||||
const medication = await this.medicationModel.create({
|
||||
id: uuidv4(),
|
||||
userId,
|
||||
name: createDto.name,
|
||||
photoUrl: createDto.photoUrl,
|
||||
form: createDto.form,
|
||||
dosageValue: createDto.dosageValue,
|
||||
dosageUnit: createDto.dosageUnit,
|
||||
timesPerDay: createDto.timesPerDay,
|
||||
medicationTimes: createDto.medicationTimes,
|
||||
repeatPattern: createDto.repeatPattern,
|
||||
startDate: new Date(createDto.startDate),
|
||||
endDate: createDto.endDate ? new Date(createDto.endDate) : null,
|
||||
note: createDto.note,
|
||||
isActive: true,
|
||||
deleted: false,
|
||||
});
|
||||
|
||||
this.logger.log(`成功创建药物 ${medication.id}`);
|
||||
return medication;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的药物列表
|
||||
*/
|
||||
async findAll(
|
||||
userId: string,
|
||||
isActive?: boolean,
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
): Promise<{ rows: Medication[]; total: number }> {
|
||||
const where: any = {
|
||||
userId,
|
||||
deleted: false,
|
||||
};
|
||||
|
||||
if (isActive !== undefined) {
|
||||
where.isActive = isActive;
|
||||
}
|
||||
|
||||
const { rows, count } = await this.medicationModel.findAndCountAll({
|
||||
where,
|
||||
limit: pageSize,
|
||||
offset: (page - 1) * pageSize,
|
||||
order: [['createdAt', 'DESC']],
|
||||
});
|
||||
|
||||
return { rows, total: count };
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取药物详情
|
||||
*/
|
||||
async findOne(id: string, userId: string): Promise<Medication> {
|
||||
const medication = await this.medicationModel.findOne({
|
||||
where: {
|
||||
id,
|
||||
deleted: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (!medication) {
|
||||
throw new NotFoundException('药物不存在');
|
||||
}
|
||||
|
||||
// 验证所有权
|
||||
if (medication.userId !== userId) {
|
||||
throw new ForbiddenException('无权访问此药物');
|
||||
}
|
||||
|
||||
return medication;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新药物信息
|
||||
*/
|
||||
async update(
|
||||
id: string,
|
||||
userId: string,
|
||||
updateDto: UpdateMedicationDto,
|
||||
): Promise<Medication> {
|
||||
const medication = await this.findOne(id, userId);
|
||||
|
||||
// 更新字段
|
||||
if (updateDto.name !== undefined) {
|
||||
medication.name = updateDto.name;
|
||||
}
|
||||
if (updateDto.photoUrl !== undefined) {
|
||||
medication.photoUrl = updateDto.photoUrl;
|
||||
}
|
||||
if (updateDto.form !== undefined) {
|
||||
medication.form = updateDto.form;
|
||||
}
|
||||
if (updateDto.dosageValue !== undefined) {
|
||||
medication.dosageValue = updateDto.dosageValue;
|
||||
}
|
||||
if (updateDto.dosageUnit !== undefined) {
|
||||
medication.dosageUnit = updateDto.dosageUnit;
|
||||
}
|
||||
if (updateDto.timesPerDay !== undefined) {
|
||||
medication.timesPerDay = updateDto.timesPerDay;
|
||||
}
|
||||
if (updateDto.medicationTimes !== undefined) {
|
||||
medication.medicationTimes = updateDto.medicationTimes;
|
||||
}
|
||||
if (updateDto.repeatPattern !== undefined) {
|
||||
medication.repeatPattern = updateDto.repeatPattern;
|
||||
}
|
||||
if (updateDto.startDate !== undefined) {
|
||||
medication.startDate = new Date(updateDto.startDate);
|
||||
}
|
||||
if (updateDto.endDate !== undefined) {
|
||||
medication.endDate = new Date(updateDto.endDate);
|
||||
}
|
||||
if (updateDto.note !== undefined) {
|
||||
medication.note = updateDto.note;
|
||||
}
|
||||
|
||||
await medication.save();
|
||||
|
||||
this.logger.log(`成功更新药物 ${id}`);
|
||||
return medication;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除药物(软删除)
|
||||
*/
|
||||
async remove(id: string, userId: string): Promise<void> {
|
||||
const medication = await this.findOne(id, userId);
|
||||
|
||||
medication.deleted = true;
|
||||
await medication.save();
|
||||
|
||||
this.logger.log(`成功删除药物 ${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停用药物
|
||||
*/
|
||||
async deactivate(id: string, userId: string): Promise<Medication> {
|
||||
const medication = await this.findOne(id, userId);
|
||||
|
||||
medication.isActive = false;
|
||||
await medication.save();
|
||||
|
||||
this.logger.log(`成功停用药物 ${id}`);
|
||||
return medication;
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活药物
|
||||
*/
|
||||
async activate(id: string, userId: string): Promise<Medication> {
|
||||
const medication = await this.findOne(id, userId);
|
||||
|
||||
medication.isActive = true;
|
||||
await medication.save();
|
||||
|
||||
this.logger.log(`成功激活药物 ${id}`);
|
||||
return medication;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user