Răsfoiți Sursa

feat(follow-up): 增强复诊管理功能

- 添加复诊原因字段以丰富记录信息
- 验证医生ID有效性并在创建时抛出自定义异常
- 当患者修改已确认的复诊申请时自动重置状态为待确认
- 将用户ID转换为字符串类型以避免前端精度丢失
- 批量查询用户信息以提高列表加载性能
- 更新接口文档描述及错误响应示例
mcbaiyun 1 lună în urmă
părinte
comite
aef0beb62a

+ 20 - 3
docs/New/复诊管理功能设计文档.md

@@ -262,6 +262,9 @@ public class UpdateFollowUpRequest {
 
     @Schema(description = "备注")
     private String notes;
+    
+    @Schema(description = "复诊原因")
+    private String reason;
 }
 ```
 
@@ -283,10 +286,10 @@ public class FollowUpResponse {
     private String id;
 
     @Schema(description = "患者用户ID")
-    private Long patientUserId;
+    private String patientUserId;
 
     @Schema(description = "医生用户ID")
-    private Long doctorUserId;
+    private String doctorUserId;
 
     @Schema(description = "预约时间")
     private LocalDateTime appointmentTime;
@@ -673,7 +676,7 @@ public class FollowUpController {
         }
     }
 
-    @Operation(summary = "更新复诊记录", description = "更新复诊记录(医生确认、取消或患者修改)")
+    @Operation(summary = "更新复诊记录", description = "更新复诊记录(医生确认、取消或患者修改)。如果患者修改了已确认复诊申请的预约时间或原因,状态将自动重置为待确认,让医生重新确认。")
     @ApiResponses(value = {
         @ApiResponse(responseCode = "200", description = "复诊记录更新成功",
             content = @Content(mediaType = "application/json",
@@ -800,6 +803,7 @@ FOLLOW_UP_STATUS_INVALID(6002, "复诊状态无效");
 ## 10. 接口调用示例
 
 ### 10.1 患者创建复诊请求
+创建复诊请求时,会验证传入的医生ID是否有效(用户存在且角色为医生)。如果无效,将返回错误码6003。
 ```
 POST /follow-up/create
 {
@@ -809,6 +813,18 @@ POST /follow-up/create
 }
 ```
 
+错误响应示例(医生ID无效):
+```json
+{
+  "code": 6003,
+  "message": "医生ID无效",
+  "data": null,
+  "timestamp": 1763570208009,
+  "requestId": "xxx",
+  "traceId": "xxx"
+}
+```
+
 ### 10.2 医生确认复诊
 ```
 POST /follow-up/update
@@ -837,6 +853,7 @@ POST /follow-up/list
    - 可以查看自己的复诊记录
    - 可以删除自己的待确认复诊请求
    - 可以修改自己的待确认复诊请求
+   - 可以更新复诊申请原因
 
 2. **医生**:
    - 可以查看分配给自己的复诊请求

+ 7 - 0
src/main/java/work/baiyun/chronicdiseaseapp/controller/FollowUpController.java

@@ -17,6 +17,7 @@ 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 work.baiyun.chronicdiseaseapp.exception.CustomException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -35,6 +36,9 @@ public class FollowUpController {
         @ApiResponse(responseCode = "200", description = "复诊请求创建成功",
             content = @Content(mediaType = "application/json",
                 schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "400", description = "医生ID无效",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
         @ApiResponse(responseCode = "500", description = "服务器内部错误",
             content = @Content(mediaType = "application/json",
                 schema = @Schema(implementation = Void.class)))
@@ -44,6 +48,9 @@ public class FollowUpController {
         try {
             followUpService.createFollowUp(req);
             return R.success(200, "复诊请求创建成功");
+        } catch (CustomException e) {
+            logger.error("create follow up request failed", e);
+            return R.fail(e.getCode(), e.getMessage());
         } catch (Exception e) {
             logger.error("create follow up request failed", e);
             return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());

+ 2 - 1
src/main/java/work/baiyun/chronicdiseaseapp/enums/ErrorCode.java

@@ -38,7 +38,8 @@ public enum ErrorCode {
     // 复诊管理相关错误码 6000-6999
     FOLLOW_UP_NOT_FOUND(6000, "复诊记录不存在"),
     FOLLOW_UP_ACCESS_DENIED(6001, "无权访问复诊记录"),
-    FOLLOW_UP_STATUS_INVALID(6002, "复诊状态无效");
+    FOLLOW_UP_STATUS_INVALID(6002, "复诊状态无效"),
+    INVALID_DOCTOR(6003, "医生ID无效");
 
     private final int code;
     private final String message;

+ 2 - 2
src/main/java/work/baiyun/chronicdiseaseapp/model/vo/FollowUpResponse.java

@@ -12,10 +12,10 @@ public class FollowUpResponse {
     private String id;
 
     @Schema(description = "患者用户ID")
-    private Long patientUserId;
+    private String patientUserId;
 
     @Schema(description = "医生用户ID")
-    private Long doctorUserId;
+    private String doctorUserId;
 
     @Schema(description = "预约时间")
     private LocalDateTime appointmentTime;

+ 3 - 0
src/main/java/work/baiyun/chronicdiseaseapp/model/vo/UpdateFollowUpRequest.java

@@ -21,4 +21,7 @@ public class UpdateFollowUpRequest {
 
     @Schema(description = "备注")
     private String notes;
+    
+    @Schema(description = "复诊原因")
+    private String reason;
 }

+ 66 - 2
src/main/java/work/baiyun/chronicdiseaseapp/service/impl/FollowUpServiceImpl.java

@@ -38,6 +38,13 @@ public class FollowUpServiceImpl implements FollowUpService {
     @Override
     public void createFollowUp(CreateFollowUpRequest request) {
         Long userId = SecurityUtils.getCurrentUserId();
+        
+        // 验证医生ID是否有效
+        UserInfo doctor = userInfoMapper.selectById(request.getDoctorUserId());
+        if (doctor == null || doctor.getRole() != PermissionGroup.DOCTOR) {
+            throw new CustomException(ErrorCode.INVALID_DOCTOR.getCode(), ErrorCode.INVALID_DOCTOR.getMessage());
+        }
+        
         FollowUp followUp = new FollowUp();
         BeanUtils.copyProperties(request, followUp);
         followUp.setPatientUserId(userId);
@@ -73,6 +80,17 @@ public class FollowUpServiceImpl implements FollowUpService {
                 "无权操作该复诊记录");
         }
         
+        // 如果患者修改了已确认的复诊申请的关键字段,重置状态为待确认
+        boolean isModifiedByPatient = role == PermissionGroup.PATIENT;
+        boolean isConfirmed = FollowUpStatus.CONFIRMED.equals(followUp.getStatus());
+        boolean hasModifiedAppointmentTime = request.getAppointmentTime() != null && 
+            !request.getAppointmentTime().equals(followUp.getAppointmentTime());
+        boolean hasModifiedReason = request.getReason() != null && 
+            !request.getReason().equals(followUp.getReason());
+        if (isModifiedByPatient && isConfirmed && (hasModifiedAppointmentTime || hasModifiedReason)) {
+            followUp.setStatus(FollowUpStatus.PENDING);
+        }
+        
         // 更新字段
         if (request.getAppointmentTime() != null) {
             followUp.setAppointmentTime(request.getAppointmentTime());
@@ -83,6 +101,9 @@ public class FollowUpServiceImpl implements FollowUpService {
         if (request.getNotes() != null) {
             followUp.setNotes(request.getNotes());
         }
+        if (request.getReason() != null) {
+            followUp.setReason(request.getReason());
+        }
         
         // 如果状态更新为已完成,则设置实际就诊时间
         if (FollowUpStatus.COMPLETED.equals(followUp.getStatus())) {
@@ -133,7 +154,16 @@ public class FollowUpServiceImpl implements FollowUpService {
             BeanUtils.copyProperties(r, resp);
             resp.setId(r.getId().toString());
             
-            // 设置用户昵称
+            // 设置用户ID为字符串类型,避免前端精度丢失
+            if (r.getPatientUserId() != null) {
+                resp.setPatientUserId(r.getPatientUserId().toString());
+            }
+            
+            if (r.getDoctorUserId() != null) {
+                resp.setDoctorUserId(r.getDoctorUserId().toString());
+            }
+            
+            // 设置昵称
             UserInfo patient = userInfoMap.get(r.getPatientUserId());
             if (patient != null) {
                 resp.setPatientNickname(patient.getNickname());
@@ -148,7 +178,6 @@ public class FollowUpServiceImpl implements FollowUpService {
             if (r.getStatus() != null) {
                 resp.setStatus(r.getStatus().getCode());
             }
-            
             return resp;
         }).collect(Collectors.toList());
 
@@ -184,10 +213,45 @@ public class FollowUpServiceImpl implements FollowUpService {
 
         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());
+            
+            // 设置用户ID为字符串类型,避免前端精度丢失
+            if (r.getPatientUserId() != null) {
+                resp.setPatientUserId(r.getPatientUserId().toString());
+            }
+            
+            if (r.getDoctorUserId() != null) {
+                resp.setDoctorUserId(r.getDoctorUserId().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());
+            }
+            
             // 设置状态码
             if (r.getStatus() != null) {
                 resp.setStatus(r.getStatus().getCode());