feat(medications): 新增完整的药物管理和服药提醒功能
实现了包含药物信息管理、服药记录追踪、统计分析、自动状态更新和推送提醒的完整药物管理系统。 核心功能: - 药物 CRUD 操作,支持多种剂型和自定义服药时间 - 惰性生成服药记录策略,查询时才生成当天记录 - 定时任务自动更新过期记录状态(每30分钟) - 服药前15分钟自动推送提醒(每5分钟检查) - 每日/范围/总体统计分析功能 - 完整的 API 文档和数据库建表脚本 技术实现: - 使用 Sequelize ORM 管理 MySQL 数据表 - 集成 @nestjs/schedule 实现定时任务 - 复用现有推送通知系统发送提醒 - 采用软删除和权限验证保障数据安全
This commit is contained in:
104
src/medications/dto/create-medication.dto.ts
Normal file
104
src/medications/dto/create-medication.dto.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsString,
|
||||
IsNotEmpty,
|
||||
IsEnum,
|
||||
IsNumber,
|
||||
IsInt,
|
||||
IsArray,
|
||||
IsDateString,
|
||||
IsOptional,
|
||||
Min,
|
||||
ArrayMinSize,
|
||||
Matches,
|
||||
} from 'class-validator';
|
||||
import { MedicationFormEnum } from '../enums/medication-form.enum';
|
||||
import { RepeatPatternEnum } from '../enums/repeat-pattern.enum';
|
||||
|
||||
/**
|
||||
* 创建药物 DTO
|
||||
*/
|
||||
export class CreateMedicationDto {
|
||||
@ApiProperty({ description: '药物名称', example: 'Metformin' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '药物照片URL',
|
||||
example: 'https://cdn.example.com/medications/med_001.jpg',
|
||||
required: false,
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
photoUrl?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '药物剂型',
|
||||
enum: MedicationFormEnum,
|
||||
example: MedicationFormEnum.CAPSULE,
|
||||
})
|
||||
@IsEnum(MedicationFormEnum)
|
||||
@IsNotEmpty()
|
||||
form: MedicationFormEnum;
|
||||
|
||||
@ApiProperty({ description: '剂量数值', example: 1 })
|
||||
@IsNumber()
|
||||
@Min(0.01)
|
||||
dosageValue: number;
|
||||
|
||||
@ApiProperty({ description: '剂量单位', example: '粒' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
dosageUnit: string;
|
||||
|
||||
@ApiProperty({ description: '每日服用次数', example: 2 })
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
timesPerDay: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '服药时间列表,格式:HH:mm',
|
||||
example: ['08:00', '20:00'],
|
||||
type: [String],
|
||||
})
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@IsString({ each: true })
|
||||
@Matches(/^([01]\d|2[0-3]):([0-5]\d)$/, {
|
||||
each: true,
|
||||
message: '服药时间格式必须为 HH:mm',
|
||||
})
|
||||
medicationTimes: string[];
|
||||
|
||||
@ApiProperty({
|
||||
description: '重复模式',
|
||||
enum: RepeatPatternEnum,
|
||||
example: RepeatPatternEnum.DAILY,
|
||||
})
|
||||
@IsEnum(RepeatPatternEnum)
|
||||
@IsNotEmpty()
|
||||
repeatPattern: RepeatPatternEnum;
|
||||
|
||||
@ApiProperty({
|
||||
description: '开始日期,ISO 8601 格式',
|
||||
example: '2025-01-01T00:00:00.000Z',
|
||||
})
|
||||
@IsDateString()
|
||||
@IsNotEmpty()
|
||||
startDate: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '结束日期,ISO 8601 格式(可选)',
|
||||
example: '2025-12-31T23:59:59.999Z',
|
||||
required: false,
|
||||
})
|
||||
@IsDateString()
|
||||
@IsOptional()
|
||||
endDate?: string;
|
||||
|
||||
@ApiProperty({ description: '备注信息', example: '饭后服用', required: false })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
note?: string;
|
||||
}
|
||||
28
src/medications/dto/medication-query.dto.ts
Normal file
28
src/medications/dto/medication-query.dto.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsOptional, IsString, IsBoolean } from 'class-validator';
|
||||
import { Transform } from 'class-transformer';
|
||||
|
||||
/**
|
||||
* 查询药物列表 DTO
|
||||
*/
|
||||
export class MedicationQueryDto {
|
||||
@ApiProperty({
|
||||
description: '是否只获取激活的药物',
|
||||
example: true,
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Transform(({ value }) => value === 'true')
|
||||
isActive?: boolean;
|
||||
|
||||
@ApiProperty({ description: '页码', example: 1, required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
page?: string;
|
||||
|
||||
@ApiProperty({ description: '每页数量', example: 20, required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
pageSize?: string;
|
||||
}
|
||||
53
src/medications/dto/medication-record-query.dto.ts
Normal file
53
src/medications/dto/medication-record-query.dto.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsOptional, IsString, IsDateString, IsEnum } from 'class-validator';
|
||||
import { MedicationStatusEnum } from '../enums/medication-status.enum';
|
||||
|
||||
/**
|
||||
* 查询服药记录 DTO
|
||||
*/
|
||||
export class MedicationRecordQueryDto {
|
||||
@ApiProperty({
|
||||
description: '指定日期(YYYY-MM-DD)',
|
||||
example: '2025-01-15',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
date?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '开始日期(YYYY-MM-DD)',
|
||||
example: '2025-01-01',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
startDate?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '结束日期(YYYY-MM-DD)',
|
||||
example: '2025-01-31',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
endDate?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '指定药物ID',
|
||||
example: 'med_001',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
medicationId?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '状态筛选',
|
||||
enum: MedicationStatusEnum,
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsEnum(MedicationStatusEnum)
|
||||
status?: MedicationStatusEnum;
|
||||
}
|
||||
59
src/medications/dto/medication-stats.dto.ts
Normal file
59
src/medications/dto/medication-stats.dto.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsNotEmpty } from 'class-validator';
|
||||
|
||||
/**
|
||||
* 每日统计查询 DTO
|
||||
*/
|
||||
export class DailyStatsQueryDto {
|
||||
@ApiProperty({
|
||||
description: '日期(YYYY-MM-DD)',
|
||||
example: '2025-01-15',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
date: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期范围统计查询 DTO
|
||||
*/
|
||||
export class RangeStatsQueryDto {
|
||||
@ApiProperty({
|
||||
description: '开始日期(YYYY-MM-DD)',
|
||||
example: '2025-01-01',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
startDate: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '结束日期(YYYY-MM-DD)',
|
||||
example: '2025-01-31',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
endDate: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 每日统计响应 DTO
|
||||
*/
|
||||
export class DailyMedicationStatsDto {
|
||||
@ApiProperty({ description: '日期', example: '2025-01-15' })
|
||||
date: string;
|
||||
|
||||
@ApiProperty({ description: '计划服药总次数', example: 6 })
|
||||
totalScheduled: number;
|
||||
|
||||
@ApiProperty({ description: '已服用次数', example: 4 })
|
||||
taken: number;
|
||||
|
||||
@ApiProperty({ description: '已错过次数', example: 1 })
|
||||
missed: number;
|
||||
|
||||
@ApiProperty({ description: '待服用次数', example: 1 })
|
||||
upcoming: number;
|
||||
|
||||
@ApiProperty({ description: '完成率(百分比)', example: 66.67 })
|
||||
completionRate: number;
|
||||
}
|
||||
16
src/medications/dto/skip-medication.dto.ts
Normal file
16
src/medications/dto/skip-medication.dto.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsOptional, IsString } from 'class-validator';
|
||||
|
||||
/**
|
||||
* 跳过服药 DTO
|
||||
*/
|
||||
export class SkipMedicationDto {
|
||||
@ApiProperty({
|
||||
description: '跳过原因或备注',
|
||||
example: '今天状态不好,暂时跳过',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
note?: string;
|
||||
}
|
||||
16
src/medications/dto/take-medication.dto.ts
Normal file
16
src/medications/dto/take-medication.dto.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsOptional, IsDateString } from 'class-validator';
|
||||
|
||||
/**
|
||||
* 标记服药 DTO
|
||||
*/
|
||||
export class TakeMedicationDto {
|
||||
@ApiProperty({
|
||||
description: '实际服药时间,ISO 8601 格式(可选,默认为当前时间)',
|
||||
example: '2025-01-15T08:10:00.000Z',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
actualTime?: string;
|
||||
}
|
||||
35
src/medications/dto/update-medication-record.dto.ts
Normal file
35
src/medications/dto/update-medication-record.dto.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsOptional, IsDateString, IsEnum, IsString } from 'class-validator';
|
||||
import { MedicationStatusEnum } from '../enums/medication-status.enum';
|
||||
|
||||
/**
|
||||
* 更新服药记录 DTO
|
||||
*/
|
||||
export class UpdateMedicationRecordDto {
|
||||
@ApiProperty({
|
||||
description: '服药状态',
|
||||
enum: MedicationStatusEnum,
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsEnum(MedicationStatusEnum)
|
||||
status?: MedicationStatusEnum;
|
||||
|
||||
@ApiProperty({
|
||||
description: '实际服药时间,ISO 8601 格式',
|
||||
example: '2025-01-15T08:15:00.000Z',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
actualTime?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '备注',
|
||||
example: '延迟服用',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
note?: string;
|
||||
}
|
||||
8
src/medications/dto/update-medication.dto.ts
Normal file
8
src/medications/dto/update-medication.dto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateMedicationDto } from './create-medication.dto';
|
||||
|
||||
/**
|
||||
* 更新药物 DTO
|
||||
* 继承创建 DTO,所有字段都是可选的
|
||||
*/
|
||||
export class UpdateMedicationDto extends PartialType(CreateMedicationDto) {}
|
||||
Reference in New Issue
Block a user