|
@@ -0,0 +1,730 @@
|
|
|
|
|
+# 复诊管理功能设计文档
|
|
|
|
|
+
|
|
|
|
|
+## 1. 功能概述
|
|
|
|
|
+
|
|
|
|
|
+复诊管理功能旨在为患者和医生提供一个便捷的复诊预约和管理平台。该功能允许患者发起复诊请求,医生审核并安排复诊时间,双方可以查看和管理复诊记录。
|
|
|
|
|
+
|
|
|
|
|
+## 2. 数据库设计
|
|
|
|
|
+
|
|
|
|
|
+### 2.1 复诊记录表 (t_follow_up)
|
|
|
|
|
+
|
|
|
|
|
+根据项目数据库表设计规范,创建复诊记录表:
|
|
|
|
|
+
|
|
|
|
|
+| 字段名 | 类型 | 描述 |
|
|
|
|
|
+|--------|------|------|
|
|
|
|
|
+| id | BIGINT | 主键ID |
|
|
|
|
|
+| patient_user_id | BIGINT | 患者用户ID |
|
|
|
|
|
+| doctor_user_id | BIGINT | 医生用户ID |
|
|
|
|
|
+| appointment_time | DATETIME | 预约时间 |
|
|
|
|
|
+| actual_time | DATETIME | 实际就诊时间 |
|
|
|
|
|
+| status | VARCHAR(20) | 复诊状态 (PENDING-待确认, CONFIRMED-已确认, CANCELLED-已取消, COMPLETED-已完成) |
|
|
|
|
|
+| reason | TEXT | 复诊原因 |
|
|
|
|
|
+| notes | TEXT | 备注 |
|
|
|
|
|
+| create_user | BIGINT | 创建者ID |
|
|
|
|
|
+| create_time | DATETIME | 创建时间 |
|
|
|
|
|
+| update_user | BIGINT | 更新者ID |
|
|
|
|
|
+| update_time | DATETIME | 更新时间 |
|
|
|
|
|
+| version | INT | 版本号(乐观锁) |
|
|
|
|
|
+| remark | VARCHAR(255) | 备注 |
|
|
|
|
|
+
|
|
|
|
|
+## 3. 实体类设计
|
|
|
|
|
+
|
|
|
|
|
+### 3.1 PO实体类 (FollowUp.java)
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+package work.baiyun.chronicdiseaseapp.model.po;
|
|
|
|
|
+
|
|
|
|
|
+import com.baomidou.mybatisplus.annotation.TableField;
|
|
|
|
|
+import com.baomidou.mybatisplus.annotation.TableName;
|
|
|
|
|
+import io.swagger.v3.oas.annotations.media.Schema;
|
|
|
|
|
+import lombok.Data;
|
|
|
|
|
+import lombok.EqualsAndHashCode;
|
|
|
|
|
+
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
|
+
|
|
|
|
|
+@Schema(description = "复诊记录表")
|
|
|
|
|
+@TableName("t_follow_up")
|
|
|
|
|
+@Data
|
|
|
|
|
+@EqualsAndHashCode(callSuper = false)
|
|
|
|
|
+public class FollowUp extends BaseEntity {
|
|
|
|
|
+ @Schema(description = "患者用户ID")
|
|
|
|
|
+ @TableField("patient_user_id")
|
|
|
|
|
+ private Long patientUserId;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "医生用户ID")
|
|
|
|
|
+ @TableField("doctor_user_id")
|
|
|
|
|
+ private Long doctorUserId;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "预约时间")
|
|
|
|
|
+ @TableField("appointment_time")
|
|
|
|
|
+ private LocalDateTime appointmentTime;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "实际就诊时间")
|
|
|
|
|
+ @TableField("actual_time")
|
|
|
|
|
+ private LocalDateTime actualTime;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "复诊状态 (PENDING-待确认, CONFIRMED-已确认, CANCELLED-已取消, COMPLETED-已完成)")
|
|
|
|
|
+ @TableField("status")
|
|
|
|
|
+ private String status;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "复诊原因")
|
|
|
|
|
+ @TableField("reason")
|
|
|
|
|
+ private String reason;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "备注")
|
|
|
|
|
+ @TableField("notes")
|
|
|
|
|
+ private String notes;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 3.2 VO对象
|
|
|
|
|
+
|
|
|
|
|
+#### 3.2.1 请求对象
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// CreateFollowUpRequest.java
|
|
|
|
|
+package work.baiyun.chronicdiseaseapp.model.vo;
|
|
|
|
|
+
|
|
|
|
|
+import io.swagger.v3.oas.annotations.media.Schema;
|
|
|
|
|
+import lombok.Data;
|
|
|
|
|
+
|
|
|
|
|
+import jakarta.validation.constraints.*;
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
|
+
|
|
|
|
|
+@Schema(description = "创建复诊请求")
|
|
|
|
|
+@Data
|
|
|
|
|
+public class CreateFollowUpRequest {
|
|
|
|
|
+ @Schema(description = "医生用户ID", required = true)
|
|
|
|
|
+ @NotNull(message = "医生ID不能为空")
|
|
|
|
|
+ private Long doctorUserId;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "预约时间", required = true)
|
|
|
|
|
+ @NotNull(message = "预约时间不能为空")
|
|
|
|
|
+ private LocalDateTime appointmentTime;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "复诊原因")
|
|
|
|
|
+ private String reason;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// UpdateFollowUpRequest.java
|
|
|
|
|
+package work.baiyun.chronicdiseaseapp.model.vo;
|
|
|
|
|
+
|
|
|
|
|
+import io.swagger.v3.oas.annotations.media.Schema;
|
|
|
|
|
+import lombok.Data;
|
|
|
|
|
+
|
|
|
|
|
+import jakarta.validation.constraints.*;
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
|
+
|
|
|
|
|
+@Schema(description = "更新复诊请求")
|
|
|
|
|
+@Data
|
|
|
|
|
+public class UpdateFollowUpRequest {
|
|
|
|
|
+ @Schema(description = "复诊记录ID", required = true)
|
|
|
|
|
+ @NotNull(message = "复诊记录ID不能为空")
|
|
|
|
|
+ private Long id;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "预约时间")
|
|
|
|
|
+ private LocalDateTime appointmentTime;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "复诊状态")
|
|
|
|
|
+ private String status;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "备注")
|
|
|
|
|
+ private String notes;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 3.2.2 响应对象
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// FollowUpResponse.java
|
|
|
|
|
+package work.baiyun.chronicdiseaseapp.model.vo;
|
|
|
|
|
+
|
|
|
|
|
+import io.swagger.v3.oas.annotations.media.Schema;
|
|
|
|
|
+import lombok.Data;
|
|
|
|
|
+
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
|
+
|
|
|
|
|
+@Schema(description = "复诊记录响应")
|
|
|
|
|
+@Data
|
|
|
|
|
+public class FollowUpResponse {
|
|
|
|
|
+ @Schema(description = "记录ID")
|
|
|
|
|
+ private String id;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "患者用户ID")
|
|
|
|
|
+ private Long patientUserId;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "医生用户ID")
|
|
|
|
|
+ private Long doctorUserId;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "预约时间")
|
|
|
|
|
+ private LocalDateTime appointmentTime;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "实际就诊时间")
|
|
|
|
|
+ private LocalDateTime actualTime;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "复诊状态")
|
|
|
|
|
+ private String status;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "复诊原因")
|
|
|
|
|
+ private String reason;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "备注")
|
|
|
|
|
+ private String notes;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "创建时间")
|
|
|
|
|
+ private LocalDateTime createTime;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "患者昵称")
|
|
|
|
|
+ private String patientNickname;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "医生昵称")
|
|
|
|
|
+ private String doctorNickname;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// FollowUpPageResponse.java
|
|
|
|
|
+package work.baiyun.chronicdiseaseapp.model.vo;
|
|
|
|
|
+
|
|
|
|
|
+import io.swagger.v3.oas.annotations.media.Schema;
|
|
|
|
|
+import lombok.Data;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+
|
|
|
|
|
+@Schema(description = "复诊记录分页响应")
|
|
|
|
|
+@Data
|
|
|
|
|
+public class FollowUpPageResponse {
|
|
|
|
|
+ @Schema(description = "数据列表")
|
|
|
|
|
+ private List<FollowUpResponse> records;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "总数")
|
|
|
|
|
+ private long total;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "每页大小")
|
|
|
|
|
+ private long size;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "当前页码")
|
|
|
|
|
+ private long current;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "总页数")
|
|
|
|
|
+ private long pages;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 4. Mapper接口设计
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// FollowUpMapper.java
|
|
|
|
|
+package work.baiyun.chronicdiseaseapp.mapper;
|
|
|
|
|
+
|
|
|
|
|
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.po.FollowUp;
|
|
|
|
|
+import org.apache.ibatis.annotations.Mapper;
|
|
|
|
|
+
|
|
|
|
|
+@Mapper
|
|
|
|
|
+public interface FollowUpMapper extends BaseMapper<FollowUp> {
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 5. Service层设计
|
|
|
|
|
+
|
|
|
|
|
+### 5.1 接口定义
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// FollowUpService.java
|
|
|
|
|
+package work.baiyun.chronicdiseaseapp.service;
|
|
|
|
|
+
|
|
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.BaseQueryRequest;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.FollowUpResponse;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.CreateFollowUpRequest;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.UpdateFollowUpRequest;
|
|
|
|
|
+
|
|
|
|
|
+public interface FollowUpService {
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 患者创建复诊请求
|
|
|
|
|
+ */
|
|
|
|
|
+ void createFollowUp(CreateFollowUpRequest request);
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 更新复诊记录(医生确认、取消或患者修改)
|
|
|
|
|
+ */
|
|
|
|
|
+ void updateFollowUp(UpdateFollowUpRequest request);
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 分页查询当前用户的复诊记录
|
|
|
|
|
+ */
|
|
|
|
|
+ Page<FollowUpResponse> listFollowUps(BaseQueryRequest request);
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 医生分页查询患者的复诊记录
|
|
|
|
|
+ */
|
|
|
|
|
+ Page<FollowUpResponse> listFollowUpsByPatient(Long patientUserId, BaseQueryRequest request);
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 删除复诊记录
|
|
|
|
|
+ */
|
|
|
|
|
+ void deleteFollowUp(Long id);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 5.2 实现类
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// FollowUpServiceImpl.java
|
|
|
|
|
+package work.baiyun.chronicdiseaseapp.service.impl;
|
|
|
|
|
+
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.mapper.FollowUpMapper;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.mapper.UserInfoMapper;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.po.FollowUp;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.po.UserInfo;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.BaseQueryRequest;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.FollowUpResponse;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.CreateFollowUpRequest;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.UpdateFollowUpRequest;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.service.FollowUpService;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.util.SecurityUtils;
|
|
|
|
|
+
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.Set;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
+
|
|
|
|
|
+@Service
|
|
|
|
|
+public class FollowUpServiceImpl implements FollowUpService {
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FollowUpMapper followUpMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private UserInfoMapper userInfoMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void createFollowUp(CreateFollowUpRequest request) {
|
|
|
|
|
+ Long userId = SecurityUtils.getCurrentUserId();
|
|
|
|
|
+ FollowUp followUp = new FollowUp();
|
|
|
|
|
+ BeanUtils.copyProperties(request, followUp);
|
|
|
|
|
+ followUp.setPatientUserId(userId);
|
|
|
|
|
+ followUp.setStatus("PENDING"); // 默认状态为待确认
|
|
|
|
|
+ followUpMapper.insert(followUp);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void updateFollowUp(UpdateFollowUpRequest request) {
|
|
|
|
|
+ Long userId = SecurityUtils.getCurrentUserId();
|
|
|
|
|
+ FollowUp followUp = followUpMapper.selectById(request.getId());
|
|
|
|
|
+
|
|
|
|
|
+ if (followUp == null) {
|
|
|
|
|
+ throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
|
|
|
|
|
+ work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_NOT_FOUND.getCode(),
|
|
|
|
|
+ "复诊记录不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 权限检查:患者只能修改自己的记录,医生只能修改分配给自己的记录
|
|
|
|
|
+ work.baiyun.chronicdiseaseapp.enums.PermissionGroup role = SecurityUtils.getCurrentUserRole();
|
|
|
|
|
+ if (role == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.PATIENT &&
|
|
|
|
|
+ !followUp.getPatientUserId().equals(userId)) {
|
|
|
|
|
+ throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
|
|
|
|
|
+ work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_ACCESS_DENIED.getCode(),
|
|
|
|
|
+ "无权操作该复诊记录");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ((role == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.DOCTOR ||
|
|
|
|
|
+ role == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.SYS_ADMIN) &&
|
|
|
|
|
+ !followUp.getDoctorUserId().equals(userId)) {
|
|
|
|
|
+ throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
|
|
|
|
|
+ work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_ACCESS_DENIED.getCode(),
|
|
|
|
|
+ "无权操作该复诊记录");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 更新字段
|
|
|
|
|
+ if (request.getAppointmentTime() != null) {
|
|
|
|
|
+ followUp.setAppointmentTime(request.getAppointmentTime());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (request.getStatus() != null) {
|
|
|
|
|
+ followUp.setStatus(request.getStatus());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (request.getNotes() != null) {
|
|
|
|
|
+ followUp.setNotes(request.getNotes());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果状态更新为已完成,则设置实际就诊时间
|
|
|
|
|
+ if ("COMPLETED".equals(request.getStatus())) {
|
|
|
|
|
+ followUp.setActualTime(java.time.LocalDateTime.now());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ followUpMapper.updateById(followUp);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Page<FollowUpResponse> listFollowUps(BaseQueryRequest request) {
|
|
|
|
|
+ Long userId = SecurityUtils.getCurrentUserId();
|
|
|
|
|
+ work.baiyun.chronicdiseaseapp.enums.PermissionGroup role = SecurityUtils.getCurrentUserRole();
|
|
|
|
|
+
|
|
|
|
|
+ Page<FollowUp> page = new Page<>(request.getPageNum(), request.getPageSize());
|
|
|
|
|
+ LambdaQueryWrapper<FollowUp> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+
|
|
|
|
|
+ // 根据用户角色查询不同的数据
|
|
|
|
|
+ if (role == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.PATIENT) {
|
|
|
|
|
+ wrapper.eq(FollowUp::getPatientUserId, userId);
|
|
|
|
|
+ } else if (role == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.DOCTOR ||
|
|
|
|
|
+ role == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.SYS_ADMIN) {
|
|
|
|
|
+ wrapper.eq(FollowUp::getDoctorUserId, userId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ wrapper.ge(request.getStartTime() != null, FollowUp::getCreateTime, request.getStartTime())
|
|
|
|
|
+ .le(request.getEndTime() != null, FollowUp::getCreateTime, request.getEndTime())
|
|
|
|
|
+ .orderByDesc(FollowUp::getCreateTime);
|
|
|
|
|
+
|
|
|
|
|
+ Page<FollowUp> result = followUpMapper.selectPage(page, wrapper);
|
|
|
|
|
+
|
|
|
|
|
+ // 批量查询用户信息
|
|
|
|
|
+ Set<Long> userIds = result.getRecords().stream()
|
|
|
|
|
+ .flatMap(r -> java.util.stream.Stream.of(r.getPatientUserId(), r.getDoctorUserId()))
|
|
|
|
|
+ .filter(id -> id != null)
|
|
|
|
|
+ .collect(Collectors.toSet());
|
|
|
|
|
+
|
|
|
|
|
+ Map<Long, UserInfo> userInfoMap;
|
|
|
|
|
+ if (userIds.isEmpty()) {
|
|
|
|
|
+ userInfoMap = java.util.Collections.emptyMap();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ List<UserInfo> userInfos = userInfoMapper.selectBatchIds(userIds);
|
|
|
|
|
+ userInfoMap = userInfos.stream().collect(Collectors.toMap(UserInfo::getId, u -> u));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<FollowUpResponse> responses = result.getRecords().stream().map(r -> {
|
|
|
|
|
+ FollowUpResponse resp = new FollowUpResponse();
|
|
|
|
|
+ BeanUtils.copyProperties(r, resp);
|
|
|
|
|
+ resp.setId(r.getId().toString());
|
|
|
|
|
+
|
|
|
|
|
+ // 设置用户昵称
|
|
|
|
|
+ UserInfo patient = userInfoMap.get(r.getPatientUserId());
|
|
|
|
|
+ if (patient != null) {
|
|
|
|
|
+ resp.setPatientNickname(patient.getNickname());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ UserInfo doctor = userInfoMap.get(r.getDoctorUserId());
|
|
|
|
|
+ if (doctor != null) {
|
|
|
|
|
+ resp.setDoctorNickname(doctor.getNickname());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return resp;
|
|
|
|
|
+ }).collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ Page<FollowUpResponse> responsePage = new Page<>();
|
|
|
|
|
+ responsePage.setRecords(responses);
|
|
|
|
|
+ responsePage.setCurrent(result.getCurrent());
|
|
|
|
|
+ responsePage.setSize(result.getSize());
|
|
|
|
|
+ responsePage.setTotal(result.getTotal());
|
|
|
|
|
+ responsePage.setPages(result.getPages());
|
|
|
|
|
+ return responsePage;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Page<FollowUpResponse> listFollowUpsByPatient(Long patientUserId, BaseQueryRequest request) {
|
|
|
|
|
+ // 检查绑定关系
|
|
|
|
|
+ Long userId = SecurityUtils.getCurrentUserId();
|
|
|
|
|
+ work.baiyun.chronicdiseaseapp.enums.PermissionGroup role = SecurityUtils.getCurrentUserRole();
|
|
|
|
|
+
|
|
|
|
|
+ // 只有医生和管理员可以查询其他患者的复诊记录
|
|
|
|
|
+ if (role != work.baiyun.chronicdiseaseapp.enums.PermissionGroup.DOCTOR &&
|
|
|
|
|
+ role != work.baiyun.chronicdiseaseapp.enums.PermissionGroup.SYS_ADMIN) {
|
|
|
|
|
+ throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
|
|
|
|
|
+ work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_ACCESS_DENIED.getCode(),
|
|
|
|
|
+ "无权查询其他患者的复诊记录");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Page<FollowUp> page = new Page<>(request.getPageNum(), request.getPageSize());
|
|
|
|
|
+ LambdaQueryWrapper<FollowUp> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ wrapper.eq(FollowUp::getPatientUserId, patientUserId)
|
|
|
|
|
+ .ge(request.getStartTime() != null, FollowUp::getCreateTime, request.getStartTime())
|
|
|
|
|
+ .le(request.getEndTime() != null, FollowUp::getCreateTime, request.getEndTime())
|
|
|
|
|
+ .orderByDesc(FollowUp::getCreateTime);
|
|
|
|
|
+
|
|
|
|
|
+ Page<FollowUp> result = followUpMapper.selectPage(page, wrapper);
|
|
|
|
|
+
|
|
|
|
|
+ List<FollowUpResponse> responses = result.getRecords().stream().map(r -> {
|
|
|
|
|
+ FollowUpResponse resp = new FollowUpResponse();
|
|
|
|
|
+ BeanUtils.copyProperties(r, resp);
|
|
|
|
|
+ resp.setId(r.getId().toString());
|
|
|
|
|
+ return resp;
|
|
|
|
|
+ }).collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ Page<FollowUpResponse> responsePage = new Page<>();
|
|
|
|
|
+ responsePage.setRecords(responses);
|
|
|
|
|
+ responsePage.setCurrent(result.getCurrent());
|
|
|
|
|
+ responsePage.setSize(result.getSize());
|
|
|
|
|
+ responsePage.setTotal(result.getTotal());
|
|
|
|
|
+ responsePage.setPages(result.getPages());
|
|
|
|
|
+ return responsePage;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void deleteFollowUp(Long id) {
|
|
|
|
|
+ Long userId = SecurityUtils.getCurrentUserId();
|
|
|
|
|
+ FollowUp followUp = followUpMapper.selectById(id);
|
|
|
|
|
+
|
|
|
|
|
+ if (followUp == null) {
|
|
|
|
|
+ throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
|
|
|
|
|
+ work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_NOT_FOUND.getCode(),
|
|
|
|
|
+ "复诊记录不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 只有患者本人才能删除自己的复诊记录
|
|
|
|
|
+ if (!followUp.getPatientUserId().equals(userId)) {
|
|
|
|
|
+ throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
|
|
|
|
|
+ work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_ACCESS_DENIED.getCode(),
|
|
|
|
|
+ "无权删除该复诊记录");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ followUpMapper.deleteById(id);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 6. Controller层设计
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// FollowUpController.java
|
|
|
|
|
+package work.baiyun.chronicdiseaseapp.controller;
|
|
|
|
|
+
|
|
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
|
|
+import io.swagger.v3.oas.annotations.Operation;
|
|
|
|
|
+import io.swagger.v3.oas.annotations.tags.Tag;
|
|
|
|
|
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|
|
|
|
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
|
|
|
|
+import io.swagger.v3.oas.annotations.media.Content;
|
|
|
|
|
+import io.swagger.v3.oas.annotations.media.Schema;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.http.MediaType;
|
|
|
|
|
+import org.springframework.web.bind.annotation.*;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.common.R;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.BaseQueryRequest;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.FollowUpResponse;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.CreateFollowUpRequest;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.UpdateFollowUpRequest;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.service.FollowUpService;
|
|
|
|
|
+import work.baiyun.chronicdiseaseapp.enums.ErrorCode;
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
|
+
|
|
|
|
|
+@RestController
|
|
|
|
|
+@RequestMapping("/follow-up")
|
|
|
|
|
+@Tag(name = "复诊管理", description = "复诊预约与管理相关接口")
|
|
|
|
|
+public class FollowUpController {
|
|
|
|
|
+
|
|
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(FollowUpController.class);
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FollowUpService followUpService;
|
|
|
|
|
+
|
|
|
|
|
+ @Operation(summary = "创建复诊请求", description = "患者创建复诊预约请求")
|
|
|
|
|
+ @ApiResponses(value = {
|
|
|
|
|
+ @ApiResponse(responseCode = "200", description = "复诊请求创建成功",
|
|
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
|
|
+ schema = @Schema(implementation = Void.class))),
|
|
|
|
|
+ @ApiResponse(responseCode = "500", description = "服务器内部错误",
|
|
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
|
|
+ schema = @Schema(implementation = Void.class)))
|
|
|
|
|
+ })
|
|
|
|
|
+ @PostMapping(path = "/create", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
|
|
|
|
+ public R<?> create(@RequestBody CreateFollowUpRequest req) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ followUpService.createFollowUp(req);
|
|
|
|
|
+ return R.success(200, "复诊请求创建成功");
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("create follow up request failed", e);
|
|
|
|
|
+ return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Operation(summary = "更新复诊记录", description = "更新复诊记录(医生确认、取消或患者修改)")
|
|
|
|
|
+ @ApiResponses(value = {
|
|
|
|
|
+ @ApiResponse(responseCode = "200", description = "复诊记录更新成功",
|
|
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
|
|
+ schema = @Schema(implementation = Void.class))),
|
|
|
|
|
+ @ApiResponse(responseCode = "500", description = "服务器内部错误",
|
|
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
|
|
+ schema = @Schema(implementation = Void.class)))
|
|
|
|
|
+ })
|
|
|
|
|
+ @PostMapping(path = "/update", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
|
|
|
|
+ public R<?> update(@RequestBody UpdateFollowUpRequest req) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ followUpService.updateFollowUp(req);
|
|
|
|
|
+ return R.success(200, "复诊记录更新成功");
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("update follow up request failed", e);
|
|
|
|
|
+ return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Operation(summary = "分页查询复诊记录", description = "根据时间范围和分页参数查询复诊记录")
|
|
|
|
|
+ @ApiResponses(value = {
|
|
|
|
|
+ @ApiResponse(responseCode = "200", description = "成功查询复诊记录列表",
|
|
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
|
|
+ schema = @Schema(implementation = work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse.class))),
|
|
|
|
|
+ @ApiResponse(responseCode = "500", description = "服务器内部错误",
|
|
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
|
|
+ schema = @Schema(implementation = Void.class)))
|
|
|
|
|
+ })
|
|
|
|
|
+ @PostMapping(path = "/list", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
|
|
|
|
+ public R<?> list(@RequestBody BaseQueryRequest req) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Page<FollowUpResponse> page = followUpService.listFollowUps(req);
|
|
|
|
|
+ work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse vo = new work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse();
|
|
|
|
|
+ vo.setRecords(page.getRecords());
|
|
|
|
|
+ vo.setTotal(page.getTotal());
|
|
|
|
|
+ vo.setSize(page.getSize());
|
|
|
|
|
+ vo.setCurrent(page.getCurrent());
|
|
|
|
|
+ vo.setPages(page.getPages());
|
|
|
|
|
+ return R.success(200, "ok", vo);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("list follow up records failed", e);
|
|
|
|
|
+ return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Operation(summary = "医生分页查询患者复诊记录", description = "医生查询患者复诊记录(需有绑定关系或权限)")
|
|
|
|
|
+ @ApiResponses(value = {
|
|
|
|
|
+ @ApiResponse(responseCode = "200", description = "成功查询复诊记录列表",
|
|
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
|
|
+ schema = @Schema(implementation = work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse.class))),
|
|
|
|
|
+ @ApiResponse(responseCode = "403", description = "无权限访问",
|
|
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
|
|
+ schema = @Schema(implementation = Void.class)))
|
|
|
|
|
+ })
|
|
|
|
|
+ @PostMapping(path = "/list-by-patient", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
|
|
|
|
+ public R<?> listByPatient(Long patientUserId, @RequestBody BaseQueryRequest req) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Page<FollowUpResponse> page = followUpService.listFollowUpsByPatient(patientUserId, req);
|
|
|
|
|
+ work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse vo = new work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse();
|
|
|
|
|
+ vo.setRecords(page.getRecords());
|
|
|
|
|
+ vo.setTotal(page.getTotal());
|
|
|
|
|
+ vo.setSize(page.getSize());
|
|
|
|
|
+ vo.setCurrent(page.getCurrent());
|
|
|
|
|
+ vo.setPages(page.getPages());
|
|
|
|
|
+ return R.success(200, "ok", vo);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("list follow up records by patient failed", e);
|
|
|
|
|
+ return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Operation(summary = "删除复诊记录", description = "根据ID删除复诊记录")
|
|
|
|
|
+ @ApiResponses(value = {
|
|
|
|
|
+ @ApiResponse(responseCode = "200", description = "复诊记录删除成功",
|
|
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
|
|
+ schema = @Schema(implementation = Void.class))),
|
|
|
|
|
+ @ApiResponse(responseCode = "500", description = "服务器内部错误",
|
|
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
|
|
+ schema = @Schema(implementation = Void.class)))
|
|
|
|
|
+ })
|
|
|
|
|
+ @PostMapping(path = "/delete", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
|
|
|
|
+ public R<?> delete(@RequestBody work.baiyun.chronicdiseaseapp.model.vo.DeleteFollowUpRequest req) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ followUpService.deleteFollowUp(req.getId());
|
|
|
|
|
+ return R.success(200, "复诊记录删除成功");
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("delete follow up record failed", e);
|
|
|
|
|
+ return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 7. 请求/响应对象补充
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// DeleteFollowUpRequest.java
|
|
|
|
|
+package work.baiyun.chronicdiseaseapp.model.vo;
|
|
|
|
|
+
|
|
|
|
|
+import io.swagger.v3.oas.annotations.media.Schema;
|
|
|
|
|
+import lombok.Data;
|
|
|
|
|
+
|
|
|
|
|
+@Schema(description = "删除复诊记录请求")
|
|
|
|
|
+@Data
|
|
|
|
|
+public class DeleteFollowUpRequest {
|
|
|
|
|
+ @Schema(description = "复诊记录ID")
|
|
|
|
|
+ private Long id;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 8. 错误码补充
|
|
|
|
|
+
|
|
|
|
|
+在 ErrorCode 枚举中可能需要添加以下错误码:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// 在 ErrorCode.java 中添加
|
|
|
|
|
+FOLLOW_UP_NOT_FOUND(6000, "复诊记录不存在"),
|
|
|
|
|
+FOLLOW_UP_ACCESS_DENIED(6001, "无权访问复诊记录"),
|
|
|
|
|
+FOLLOW_UP_STATUS_INVALID(6002, "复诊状态无效");
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 9. 接口调用示例
|
|
|
|
|
+
|
|
|
|
|
+### 9.1 患者创建复诊请求
|
|
|
|
|
+```
|
|
|
|
|
+POST /follow-up/create
|
|
|
|
|
+{
|
|
|
|
|
+ "doctorUserId": 1988172854356631553,
|
|
|
|
|
+ "appointmentTime": "2025-12-01T10:00:00",
|
|
|
|
|
+ "reason": "血糖控制不佳,需要调整治疗方案"
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 9.2 医生确认复诊
|
|
|
|
|
+```
|
|
|
|
|
+POST /follow-up/update
|
|
|
|
|
+{
|
|
|
|
|
+ "id": 1988502746686595073,
|
|
|
|
|
+ "status": "CONFIRMED",
|
|
|
|
|
+ "notes": "已确认预约"
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 9.3 查询复诊记录
|
|
|
|
|
+```
|
|
|
|
|
+POST /follow-up/list
|
|
|
|
|
+{
|
|
|
|
|
+ "pageNum": 1,
|
|
|
|
|
+ "pageSize": 10,
|
|
|
|
|
+ "startTime": "2025-11-01T00:00:00",
|
|
|
|
|
+ "endTime": "2025-12-01T23:59:59"
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 10. 权限设计
|
|
|
|
|
+
|
|
|
|
|
+1. **患者**:
|
|
|
|
|
+ - 可以创建复诊请求
|
|
|
|
|
+ - 可以查看自己的复诊记录
|
|
|
|
|
+ - 可以删除自己的待确认复诊请求
|
|
|
|
|
+ - 可以修改自己的待确认复诊请求
|
|
|
|
|
+
|
|
|
|
|
+2. **医生**:
|
|
|
|
|
+ - 可以查看分配给自己的复诊请求
|
|
|
|
|
+ - 可以确认、取消或完成复诊
|
|
|
|
|
+ - 可以查看患者的复诊历史
|
|
|
|
|
+
|
|
|
|
|
+3. **系统管理员**:
|
|
|
|
|
+ - 具有医生的所有权限
|
|
|
|
|
+ - 可以查看所有复诊记录
|
|
|
|
|
+
|
|
|
|
|
+## 11. 状态流转
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+PENDING(待确认) -> CONFIRMED(已确认) -> COMPLETED(已完成)
|
|
|
|
|
+ |
|
|
|
|
|
+ v
|
|
|
|
|
+CANCELLED(已取消)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 12. 安全考虑
|
|
|
|
|
+
|
|
|
|
|
+1. 数据访问权限控制
|
|
|
|
|
+2. 参数验证和输入检查
|
|
|
|
|
+3. 防止SQL注入
|
|
|
|
|
+4. 日志记录和审计追踪
|