瀏覽代碼

益寿缘-优化对接超拼网接口

cgp 3 天之前
父節點
當前提交
8da4af2a1d
共有 22 個文件被更改,包括 858 次插入172 次删除
  1. 49 15
      fs-admin/src/main/java/com/fs/his/controller/OpenApiFsUserInformationController.java
  2. 11 0
      fs-doctor-app/src/main/java/com/fs/app/controller/UserInfoCollectionController.java
  3. 1 1
      fs-framework/src/main/java/com/fs/framework/config/SecurityConfig.java
  4. 62 0
      fs-service/src/main/java/com/fs/his/domain/FsPrescribe.java
  5. 42 0
      fs-service/src/main/java/com/fs/his/dto/CPWPatientInfoAddDTO.java
  6. 11 5
      fs-service/src/main/java/com/fs/his/dto/CPWUserInfoCollectionAddDTO.java
  7. 8 0
      fs-service/src/main/java/com/fs/his/mapper/FsPrescribeMapper.java
  8. 8 16
      fs-service/src/main/java/com/fs/his/service/impl/FsPrescribeServiceImpl.java
  9. 138 76
      fs-service/src/main/java/com/fs/his/validator/cpw/AnswerValidator.java
  10. 1 1
      fs-service/src/main/java/com/fs/his/validator/cpw/BaseInfoValidator.java
  11. 186 5
      fs-service/src/main/java/com/fs/his/validator/cpw/CollectionInfoValidator.java
  12. 4 1
      fs-service/src/main/java/com/fs/his/vo/FsPrescribeListVO.java
  13. 5 0
      fs-service/src/main/java/com/fs/his/vo/FsPrescribeVO.java
  14. 2 3
      fs-service/src/main/java/com/fs/his/vo/SubmitUserInformationVO.java
  15. 5 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsUserInformationCollection.java
  16. 5 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsUserInformationCollectionMapper.java
  17. 8 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsUserInformationCollectionService.java
  18. 19 6
      fs-service/src/main/java/com/fs/hisStore/service/IOpenApiCPWUserInformationService.java
  19. 18 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsUserInformationCollectionServiceImpl.java
  20. 203 9
      fs-service/src/main/java/com/fs/hisStore/service/impl/OpenApiCPWUserInformationServiceImpl.java
  21. 62 33
      fs-service/src/main/resources/mapper/his/FsPrescribeMapper.xml
  22. 10 1
      fs-service/src/main/resources/mapper/hisStore/FsUserInformationCollectionMapper.xml

+ 49 - 15
fs-admin/src/main/java/com/fs/his/controller/OpenApiFsUserInformationController.java

@@ -50,25 +50,48 @@ public class OpenApiFsUserInformationController extends BaseController {
     }
 
     /**
-     * 创建用户信息与地址--超拼网
+     * 提交---超拼网---患者待开方信息
      * */
-    @PostMapping("/createUserInformation")
-    public AjaxResult createUserInformationCPW(@RequestBody CPWUserAndAddressAddDTO cpwUserAndAddressAddDTO) {
+    @PostMapping("/submitPatientPrescriptionInfo")
+    public AjaxResult submitPatientPrescriptionInfo(@RequestBody CPWUserInfoCollectionAddDTO collectionAddDTO) {
         try {
             // 参数校验
-            userInformationValidator.validateBaseInfo(cpwUserAndAddressAddDTO.getUserBaseInfo());
-            userInformationValidator.validateUserAndAddress(cpwUserAndAddressAddDTO.getAddress());
-            SubmitUserInformationVO result = openApiFsUserInformationService.createUserInformationCPW(cpwUserAndAddressAddDTO);
+            userInformationValidator.validateCollection(collectionAddDTO);
+            SubmitUserInformationVO result = openApiFsUserInformationService.submitPatientPrescriptionInfo(collectionAddDTO);
             return AjaxResult.success(result);
         } catch (CustomException e) {
-            return AjaxResult.error(e.getCode(), e.getMessage());
-        } catch(Exception e) {
-            logger.error("创建用户信息与地址业务异常:", e);
-            return AjaxResult.error("系统处理异常,请稍后重试");
+            logger.error("提交---超拼网---患者待开方信息业务异常:{}", e.getMessage());
+            Map<String, Object> errorDetail = new HashMap<>();
+            errorDetail.put("errorCode", e.getCode() != null ? e.getCode() : 500);
+            errorDetail.put("errorMessage", e.getMessage());
+            errorDetail.put("timestamp", DateUtils.getTime());
+            errorDetail.put("path", "/ysy-api/template/submitPatientPrescriptionInfo");
+
+            AjaxResult result = AjaxResult.error(e.getCode() != null ? e.getCode() : 500, e.getMessage());
+            result.put("data", errorDetail);
+
+            return result;
+
+        } catch (Exception e) {
+            logger.error("提交用户采集信息系统异常:", e);
+            return AjaxResult.error("系统繁忙,请稍后重试");
         }
     }
+
+    /**
+     * 推送处方信息给第三方
+     * */
+    @PostMapping("/pushPrescription")
+    public AjaxResult pushPrescription() {
+        return AjaxResult.success();
+    }
+
+
+
+
+
     /**
-     * 提交用户采集信息
+     * 提交用户采集信息----------已弃用!!!!
      * */
     @PostMapping("/submitUserInformation")
     public AjaxResult submitUserInformation(@RequestBody CPWUserInfoCollectionAddDTO collectionAddDTO) {
@@ -97,10 +120,21 @@ public class OpenApiFsUserInformationController extends BaseController {
     }
 
     /**
-     * 推送处方信息给第三方
+     * 创建用户信息与地址--超拼网----------已弃用!!!!
      * */
-    @PostMapping("/pushPrescription")
-    public AjaxResult pushPrescription() {
-        return AjaxResult.success();
+    @PostMapping("/createUserInformation")
+    public AjaxResult createUserInformationCPW(@RequestBody CPWUserAndAddressAddDTO cpwUserAndAddressAddDTO) {
+        try {
+            // 参数校验
+            userInformationValidator.validateBaseInfo(cpwUserAndAddressAddDTO.getUserBaseInfo());
+            userInformationValidator.validateUserAndAddress(cpwUserAndAddressAddDTO.getAddress());
+            SubmitUserInformationVO result = openApiFsUserInformationService.createUserInformationCPW(cpwUserAndAddressAddDTO);
+            return AjaxResult.success(result);
+        } catch (CustomException e) {
+            return AjaxResult.error(e.getCode(), e.getMessage());
+        } catch(Exception e) {
+            logger.error("创建用户信息与地址业务异常:", e);
+            return AjaxResult.error("系统处理异常,请稍后重试");
+        }
     }
 }

+ 11 - 0
fs-doctor-app/src/main/java/com/fs/app/controller/UserInfoCollectionController.java

@@ -51,4 +51,15 @@ public class UserInfoCollectionController extends AppBaseController{
         FsUserInformationCollectionVO detail = userInfoCollectionService.getCollectionByUserId(userId);
         return R.ok().put("data", detail);
     }
+
+    /**
+     * 根据第三方用户id和采集来源查询信息采集详情
+     * @param infoSource 信息来源
+     * @param thirdPartyUserId 第三方用户id
+     * */
+    @GetMapping("/getCollectionByInfoSourceAndThirdPartyUserId/{infoSource}/{thirdPartyUserId}")
+    public R getCollectionByInfoSourceAndThirdPartyUserId(@PathVariable("infoSource") Integer infoSource,@PathVariable("thirdPartyUserId")Long thirdPartyUserId){
+        FsUserInformationCollectionVO detail = userInfoCollectionService.getCollectionByInfoSourceAndThirdPartyUserId(infoSource,thirdPartyUserId);
+        return R.ok().put("data", detail);
+    }
 }

+ 1 - 1
fs-framework/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -97,7 +97,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 // 过滤请求
                 .authorizeRequests()
                 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
-                .antMatchers("/login", "/register", "/captchaImage","/sync/order/jst/open/callback").anonymous()
+                .antMatchers("/login", "/register", "/captchaImage","/sync/order/jst/open/callback","/ysy-api/template/**").anonymous()
                 .antMatchers("/app/common/test").anonymous()
                 .antMatchers("/ad/adDyApi/authorized").anonymous()
                 .antMatchers(

+ 62 - 0
fs-service/src/main/java/com/fs/his/domain/FsPrescribe.java

@@ -188,6 +188,18 @@ public class FsPrescribe extends BaseEntity
      */
     private Long operateSecond;
 
+    /**
+     * 第三方用户id 暂时只有超拼网用户id
+     * */
+    private Long thirdPartyUserId;
+
+    /**
+     * 是否推送给第三方(0:未推送,1:已推送)
+     * */
+    private Integer isSendToThirdParty;
+
+    public FsPrescribe() {
+    }
 
     public String getPrescribeImgStoreUrl() {
         return prescribeImgStoreUrl;
@@ -568,6 +580,56 @@ public class FsPrescribe extends BaseEntity
         return auditTime;
     }
 
+    public Long getThirdPartyUserId() {
+        return thirdPartyUserId;
+    }
+
+    public void setThirdPartyUserId(Long thirdPartyUserId) {
+        this.thirdPartyUserId = thirdPartyUserId;
+    }
+
+    public Integer getDoctorConfirm() {
+        return doctorConfirm;
+    }
+
+    public void setDoctorConfirm(Integer doctorConfirm) {
+        this.doctorConfirm = doctorConfirm;
+    }
+
+    public Date getStartOperateTime() {
+        return startOperateTime;
+    }
+
+    public void setStartOperateTime(Date startOperateTime) {
+        this.startOperateTime = startOperateTime;
+    }
+
+    public Date getEndOperateTime() {
+        return endOperateTime;
+    }
+
+    public void setEndOperateTime(Date endOperateTime) {
+        this.endOperateTime = endOperateTime;
+    }
+
+    public Long getOperateSecond() {
+        return operateSecond;
+    }
+
+    public void setOperateSecond(Long operateSecond) {
+        this.operateSecond = operateSecond;
+    }
+
+    public Integer getIsSendToThirdParty() {
+        return isSendToThirdParty;
+    }
+
+    public void setIsSendToThirdParty(Integer isSendToThirdParty) {
+        this.isSendToThirdParty = isSendToThirdParty;
+    }
+
+
+
     @Override
     public String toString() {
         return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)

+ 42 - 0
fs-service/src/main/java/com/fs/his/dto/CPWPatientInfoAddDTO.java

@@ -0,0 +1,42 @@
+package com.fs.his.dto;
+
+
+import lombok.Data;
+
+/**
+ * 患者基础信息---超拼网
+ */
+@Data
+public class CPWPatientInfoAddDTO {
+    /**
+     * 患者id
+     * */
+    private Long patientId;
+
+    /**
+     * 患者姓名
+     */
+    private String patientName;
+
+    /**
+     * 患者出生日期 yyyy-mm-dd
+     */
+    private String patientBirthday;
+
+
+    /**
+     * 患者性别(1:男 2:⼥)
+     */
+    private String patientGender;
+
+    /**
+     * 体重
+     */
+    private String weight;
+
+    /**
+     * 患者电话
+     */
+    private String patientTel;
+
+}

+ 11 - 5
fs-service/src/main/java/com/fs/his/dto/CPWUserInfoCollectionAddDTO.java

@@ -11,13 +11,14 @@ import java.util.List;
 @Data
 public class CPWUserInfoCollectionAddDTO {
     /**
-     * 用户主键
-     * */
-    private Long userId;
+     * 采集信息ID
+     */
+    private Long collectionId;
+
     /**
-     * 模板问题以及答案
+     * 患者基础信息
      */
-    private List<AnswerVO> answers;
+    private CPWPatientInfoAddDTO patientInfo;
 
     /**
      * 过敏症状描述,默认无
@@ -28,4 +29,9 @@ public class CPWUserInfoCollectionAddDTO {
      * 备注(非必填)
      */
     private String remark;
+
+    /**
+     * 模板问题以及答案
+     */
+    private List<AnswerVO> answers;
 }

+ 8 - 0
fs-service/src/main/java/com/fs/his/mapper/FsPrescribeMapper.java

@@ -300,4 +300,12 @@ public interface FsPrescribeMapper
 
     @Select("SELECT d.doctor_name, dd.doctor_name doctor_drug_name  FROM fs_prescribe p LEFT JOIN fs_doctor d ON p.doctor_id = d.doctor_id LEFT JOIN fs_doctor dd ON p.drug_doctor_id = dd.doctor_id WHERE p.prescribe_id = #{prescribeId}")
     FsPrescribeListVO selectDoctorNameAndDoctorDrugNameByPrescribeId(@Param("prescribeId") Long prescribeId);
+
+    /**
+     * 查询第三方用户是否存在待开方且处于审核中的处方
+     *
+     * @param thirdPartyUserId 第三方用户ID
+     * @return 待审核的处方信息,null表示没有待审核处方
+     */
+    FsPrescribeListVO selectFsPrescribeListVOByThirdUserId(@Param("thirdPartyUserId") Long thirdPartyUserId);
 }

+ 8 - 16
fs-service/src/main/java/com/fs/his/service/impl/FsPrescribeServiceImpl.java

@@ -223,20 +223,6 @@ public class FsPrescribeServiceImpl implements IFsPrescribeService
         if (CollectionUtils.isEmpty(fsPrescribeListVOS)){
             return Collections.emptyList();
         }
-        //设置处方来源标识 医生在开方时要求区分处方的来源 哪些是信息采集表产生的
-        List<String> orderCodes=fsPrescribeListVOS.stream().map(FsPrescribeListVO::getOrderCode).collect(Collectors.toList());
-        //通过订单号批量查询信息采集进度表
-        List<FsUserInformationCollectionSchedule> schedules = scheduleMapper.selectCollectionScheduleListByOrderCodeList(orderCodes);
-        if (CollectionUtils.isEmpty(schedules)){
-            return fsPrescribeListVOS;
-        }
-        Set<String> existingOrderCodes = schedules.stream().map(FsUserInformationCollectionSchedule::getOrderCode).filter(Objects::nonNull).collect(Collectors.toSet());
-        //从信息采集进度表查询匹配的数据,对于匹配的,将处方来源标识设置为1
-        for(FsPrescribeListVO vo:fsPrescribeListVOS){
-            if (existingOrderCodes.contains(vo.getOrderCode())){
-                vo.setPrescribeSource(1);
-            }
-        }
         return  fsPrescribeListVOS;
     }
 
@@ -571,8 +557,10 @@ public class FsPrescribeServiceImpl implements IFsPrescribeService
                 throw new IllegalArgumentException(String.format("处方单 %d 对应药品为空!",prescribeId));
             }
             PrescribeXyImgParam o = new PrescribeXyImgParam();
-            o.setStatus(fsStoreOrder.getStatus());
-            o.setTotalPrice(fsStoreOrder.getPayPrice());
+            if (fsStoreOrder!=null){
+                o.setStatus(fsStoreOrder.getStatus());
+                o.setTotalPrice(fsStoreOrder.getPayPrice());
+            }
             o.setTime(f.getCreateTime());
             o.setPrescribeId(f.getPrescribeCode());
             o.setPatientName(f.getPatientName());
@@ -1248,6 +1236,10 @@ public class FsPrescribeServiceImpl implements IFsPrescribeService
         prescribeRecord.setDrugDoctorName(vo.getDoctorDrugName());
         prescribeRecordService.insertFsPrescribeRecord(prescribeRecord);
         if (param.getStatus() == 1) {
+            if (prescribe.getThirdPartyUserId()!=null){
+                //对于第三方用户的处方,药师审核通过后设置状态为未推送数据给第三方
+                map.setIsSendToThirdParty(0);
+            }
             map.setAuditReason("");
             //fsExportTaskService.updateFsExportTaskByPrescribeId(param.getPrescribeId());
             //重置处方单定时任务(药师审核通过后生成拥有药师签名的标识)

+ 138 - 76
fs-service/src/main/java/com/fs/his/validator/cpw/AnswerValidator.java

@@ -1,136 +1,198 @@
 package com.fs.his.validator.cpw;
 
 import com.fs.common.exception.CustomException;
-import com.fs.common.utils.StringUtils;
 import com.fs.his.vo.AnswerVO;
-import org.springframework.beans.factory.annotation.Autowired;
+import com.fs.his.vo.FsQuestionAndAnswerVO;
 import org.springframework.stereotype.Component;
 
-import java.util.Set;
+import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
- * 问题答案校验器
+ * 答案校验器
+ * 规则:每个问题都必须提交,但选项可以选也可以不选
  */
 @Component
-public class AnswerValidator implements Validator<AnswerVO> {
-
-    @Autowired
-    private OptionValidator optionValidator;
-
-    @Override
-    public void validate(AnswerVO answer) {
-        validateAnswer(answer, 0);
-    }
+public class AnswerValidator {
 
     /**
-     * 带索引的校验方法
+     * 校验单个答案
      */
-    public void validate(AnswerVO answer, int index) {
+    public void validate(AnswerVO answer, int index, FsQuestionAndAnswerVO template) {
         if (answer == null) {
-            throw new CustomException(String.format("第%d个问题的答案不能为空", index + 1), 400);
+            throw new CustomException("第" + (index + 1) + "个问题的答案不能为空", 400);
         }
-        validateAnswer(answer, index);
-    }
-
-    private void validateAnswer(AnswerVO answer, int index) {
-        // 1. 问题标题校验
-        validateTitle(answer.getTitle(), index);
 
-        // 2. 选项列表校验
-        validateOptions(answer);
+        // 1. 校验问题标题不能为空
+        if (answer.getTitle() == null || answer.getTitle().trim().isEmpty()) {
+            throw new CustomException("第" + (index + 1) + "个问题的标题不能为空", 400);
+        }
 
-        // 3. 已选择的值校验
-        validateSelectedValues(answer);
+        // 2. 校验排序字段不能为空
+        if (answer.getSort() == null) {
+            throw new CustomException("第" + (index + 1) + "个问题【" + answer.getTitle() + "】的排序字段不能为空", 400);
+        }
 
-        // 4. 排序字段校验
-        validateSort(answer.getSort(), answer.getTitle());
+        // 3. 校验选项列表
+        validateOptions(answer, index);
 
-        // 5. flag字段处理
-        handleFlag(answer);
-    }
+        // 4. 校验已选择的值(关键修改:允许空数组)
+        validateSelectedValues(answer, index);
 
-    /**
-     * 校验问题标题
-     */
-    private void validateTitle(String title, int index) {
-        if (StringUtils.isEmpty(title)) {
-            throw new CustomException(String.format("第%d个问题的问题名称不能为空", index + 1), 400);
-        }
-        
-        if (title.length() > 200) {
-            throw new CustomException(String.format("第%d个问题的问题名称不能超过200个字符", index + 1), 400);
+        // 5. 校验flag字段
+        if (answer.getFlag() == null) {
+            answer.setFlag(false);
         }
+
+        // 6. 如果提供了模板,校验问题是否匹配
+//        if (template != null) {
+//            validateAgainstTemplate(answer, index, template);
+//        }
     }
 
     /**
      * 校验选项列表
      */
-    private void validateOptions(AnswerVO answer) {
-        if (answer.getOptions() == null || answer.getOptions().isEmpty()) {
-            throw new CustomException(String.format("问题【%s】的选项列表不能为空", answer.getTitle()), 400);
+    private void validateOptions(AnswerVO answer, int index) {
+        List<AnswerVO.Options> options = answer.getOptions();
+        
+        if (options == null || options.isEmpty()) {
+            throw new CustomException("第" + (index + 1) + "个问题【" + answer.getTitle() + "】的选项列表不能为空", 400);
         }
 
-        // 检查选项是否有重复
-        Set<Integer> valueSet = answer.getOptions().stream()
+        // 检查选项是否有重复的value
+        long distinctValueCount = options.stream()
                 .map(AnswerVO.Options::getValue)
-                .collect(Collectors.toSet());
+                .filter(value -> value != null)
+                .distinct()
+                .count();
         
-        if (valueSet.size() != answer.getOptions().size()) {
-            throw new CustomException(String.format("问题【%s】的选项值不能重复", answer.getTitle()), 400);
+        if (distinctValueCount != options.size()) {
+            throw new CustomException("第" + (index + 1) + "个问题【" + answer.getTitle() + "】的选项值不能重复", 400);
         }
 
-        for (int j = 0; j < answer.getOptions().size(); j++) {
-            optionValidator.validate(answer.getOptions().get(j), answer.getTitle(), j);
+        // 检查每个选项的name和value
+        for (int i = 0; i < options.size(); i++) {
+            AnswerVO.Options option = options.get(i);
+            
+            if (option.getName() == null || option.getName().trim().isEmpty()) {
+                throw new CustomException("第" + (index + 1) + "个问题【" + answer.getTitle() + "】的第" + (i + 1) + "个选项名称不能为空", 400);
+            }
+            
+            if (option.getValue() == null) {
+                throw new CustomException("第" + (index + 1) + "个问题【" + answer.getTitle() + "】的第" + (i + 1) + "个选项值不能为空", 400);
+            }
         }
     }
 
     /**
      * 校验已选择的值
+     * 关键修改:允许空数组,表示该问题没选任何选项
      */
-    private void validateSelectedValues(AnswerVO answer) {
-        if (answer.getValue() == null) {
-            throw new CustomException(String.format("问题【%s】的已选择值不能为null", answer.getTitle()), 400);
+    private void validateSelectedValues(AnswerVO answer, int index) {
+        List<Integer> selectedValues = answer.getValue();
+        
+        // value字段可以为null或空数组,表示没选任何选项
+        if (selectedValues == null) {
+            answer.setValue(new java.util.ArrayList<>()); // 初始化为空数组
+            return;
         }
 
-        if (!answer.getValue().isEmpty()) {
-            Set<Integer> validOptionValues = answer.getOptions().stream()
+        // 如果选择了值,需要校验这些值是否在options中
+        if (!selectedValues.isEmpty()) {
+            // 获取所有有效的选项值
+            List<Integer> validValues = answer.getOptions().stream()
                     .map(AnswerVO.Options::getValue)
-                    .collect(Collectors.toSet());
+                    .collect(Collectors.toList());
 
-            for (Integer selectedValue : answer.getValue()) {
-                if (selectedValue == null) {
-                    throw new CustomException(String.format("问题【%s】的选择值不能为null", answer.getTitle()), 400);
+            // 检查每个选中的值是否在有效选项中
+            for (Integer value : selectedValues) {
+                if (value == null) {
+                    throw new CustomException("第" + (index + 1) + "个问题【" + answer.getTitle() + "】的选择值不能为null", 400);
                 }
-                if (!validOptionValues.contains(selectedValue)) {
-                    throw new CustomException(
-                            String.format("问题【%s】的选择值 %d 不在有效选项范围内", answer.getTitle(), selectedValue),
-                            400
-                    );
+                
+                if (!validValues.contains(value)) {
+                    throw new CustomException("第" + (index + 1) + "个问题【" + answer.getTitle() + "】的选择值【" + value + "】不在有效选项范围内", 400);
                 }
             }
+
+            // 检查是否有重复的选择
+            long distinctCount = selectedValues.stream().distinct().count();
+            if (distinctCount != selectedValues.size()) {
+                throw new CustomException("第" + (index + 1) + "个问题【" + answer.getTitle() + "】的选择值不能重复", 400);
+            }
         }
     }
 
     /**
-     * 校验排序字段
+     * 根据模板校验答案
      */
-    private void validateSort(Integer sort, String questionTitle) {
-        if (sort == null) {
-            throw new CustomException(String.format("问题【%s】的排序字段不能为空", questionTitle), 400);
+    private void validateAgainstTemplate(AnswerVO answer, int index, FsQuestionAndAnswerVO template) {
+        // 根据sort找到模板中对应的问题
+        AnswerVO templateAnswer = template.getAnswers().stream()
+                .filter(a -> a.getSort() != null && a.getSort().equals(answer.getSort()))
+                .findFirst()
+                .orElse(null);
+
+//        if (templateAnswer == null) {
+//            throw new CustomException("第" + (index + 1) + "个问题【" + answer.getTitle() + "】的排序【" + answer.getSort() + "】与模板不匹配", 400);
+//        }
+
+        // 校验标题是否一致
+        if (!templateAnswer.getTitle().equals(answer.getTitle())) {
+            throw new CustomException("第" + (index + 1) + "个问题的标题与模板不一致,应为:【" + templateAnswer.getTitle() + "】,实际为:【" + answer.getTitle() + "】", 400);
         }
-        
-        if (sort < 0) {
-            throw new CustomException(String.format("问题【%s】的排序字段不能为负数", questionTitle), 400);
+
+        // 校验选项是否匹配
+        validateOptionsMatchTemplate(answer, templateAnswer, index);
+    }
+
+    /**
+     * 校验选项是否与模板匹配
+     */
+    private void validateOptionsMatchTemplate(AnswerVO answer, AnswerVO templateAnswer, int index) {
+        Map<Integer, String> templateOptionMap = templateAnswer.getOptions().stream()
+                .collect(Collectors.toMap(AnswerVO.Options::getValue, AnswerVO.Options::getName));
+
+        for (AnswerVO.Options option : answer.getOptions()) {
+            String templateName = templateOptionMap.get(option.getValue());
+            
+            if (templateName == null) {
+                throw new CustomException("第" + (index + 1) + "个问题【" + answer.getTitle() + "】的选项值【" + option.getValue() + "】不在模板中", 400);
+            }
+            
+            if (!templateName.equals(option.getName())) {
+                throw new CustomException("第" + (index + 1) + "个问题【" + answer.getTitle() + "】的选项名称与模板不一致,值【" + option.getValue() + "】应为:【" + templateName + "】,实际为:【" + option.getName() + "】", 400);
+            }
         }
     }
 
     /**
-     * 处理flag字段
+     * 批量校验所有答案
      */
-    private void handleFlag(AnswerVO answer) {
-        if (answer.getFlag() == null) {
-            answer.setFlag(false);
+    public void validateAll(List<AnswerVO> answers, FsQuestionAndAnswerVO template) {
+        if (answers == null || answers.isEmpty()) {
+            throw new CustomException("答案列表不能为空", 400);
+        }
+
+        // 校验答案数量是否与模板一致
+        if (template != null && answers.size() != template.getAnswers().size()) {
+            throw new CustomException("答案数量与模板不匹配,模板有【" + template.getAnswers().size() + "】个问题,实际提交了【" + answers.size() + "】个", 400);
+        }
+
+        // 按sort排序后校验
+        List<AnswerVO> sortedAnswers = answers.stream()
+                .sorted((a1, a2) -> {
+                    if (a1.getSort() == null || a2.getSort() == null) {
+                        return 0;
+                    }
+                    return a1.getSort().compareTo(a2.getSort());
+                })
+                .collect(Collectors.toList());
+
+        for (int i = 0; i < sortedAnswers.size(); i++) {
+            validate(sortedAnswers.get(i), i, template);
         }
     }
 }

+ 1 - 1
fs-service/src/main/java/com/fs/his/validator/cpw/BaseInfoValidator.java

@@ -25,7 +25,7 @@ public class BaseInfoValidator implements Validator<CPWUserBaseInfoAddDTO> {
         validateUserPhone(userBaseInfo.getUserPhone());
         
         // 身份证校验
-        validateIdCard(userBaseInfo.getIdCard());
+        //validateIdCard(userBaseInfo.getIdCard());
     }
 
     /**

+ 186 - 5
fs-service/src/main/java/com/fs/his/validator/cpw/CollectionInfoValidator.java

@@ -2,11 +2,18 @@ package com.fs.his.validator.cpw;
 
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.StringUtils;
+import com.fs.his.dto.CPWPatientInfoAddDTO;
 import com.fs.his.dto.CPWUserInfoCollectionAddDTO;
 import com.fs.his.vo.AnswerVO;
+import com.fs.his.vo.FsQuestionAndAnswerVO;
+import com.fs.hisStore.service.IOpenApiCPWUserInformationService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -18,6 +25,9 @@ public class CollectionInfoValidator implements Validator<CPWUserInfoCollectionA
     @Autowired
     private AnswerValidator answerValidator;
 
+    @Autowired
+    private IOpenApiCPWUserInformationService openApiFsUserInformationService;
+
     @Override
     public void validate(CPWUserInfoCollectionAddDTO collectionAddDTO) {
         if (collectionAddDTO == null) {
@@ -25,7 +35,10 @@ public class CollectionInfoValidator implements Validator<CPWUserInfoCollectionA
         }
 
         // 校验用户ID(如果是提交采集信息时需要)
-        validateUserId(collectionAddDTO.getUserId());
+        //validateUserId(collectionAddDTO.getUserId());
+
+        // 校验患者基本信息
+        validatePatientBaseInfo(collectionAddDTO.getPatientInfo());
 
         // 校验答案列表
         validateAnswers(collectionAddDTO.getAnswers());
@@ -64,14 +77,16 @@ public class CollectionInfoValidator implements Validator<CPWUserInfoCollectionA
                 .filter(sort -> sort != null)
                 .distinct()
                 .count();
-        
+
         if (distinctSortCount != answers.size()) {
             throw new CustomException("问题的排序字段不能重复", 400);
         }
 
-        for (int i = 0; i < answers.size(); i++) {
-            answerValidator.validate(answers.get(i), i);
-        }
+        // 获取问题模板
+        FsQuestionAndAnswerVO template = openApiFsUserInformationService.getInformationTemplate();
+
+        // 使用新的批量校验方法
+        answerValidator.validateAll(answers, template);
     }
 
     /**
@@ -97,4 +112,170 @@ public class CollectionInfoValidator implements Validator<CPWUserInfoCollectionA
             throw new CustomException("备注不能超过500个字符", 400);
         }
     }
+    /**
+     * 校验患者基本信息
+     * */
+    private void validatePatientBaseInfo(CPWPatientInfoAddDTO patientInfo) {
+        if (patientInfo == null) {
+            throw new CustomException("患者基础信息不能为空", 400);
+        }
+        validatePatientName(patientInfo.getPatientName());
+        validatePhone(patientInfo.getPatientTel());
+        validatePatientBirthday(patientInfo.getPatientBirthday());
+        validatePatientGender(patientInfo.getPatientGender());
+        validatePatientWeight(patientInfo.getWeight());
+    }
+
+
+    /**
+     * 校验患者姓名
+     */
+    private void validatePatientName(String patientName) {
+        if (StringUtils.isEmpty(patientName)) {
+            throw new CustomException("患者姓名不能为空", 400);
+        }
+
+        if (patientName.length() < 2 || patientName.length() > 20) {
+            throw new CustomException("患者姓名长度必须在2-20个字符之间", 400);
+        }
+
+        if (patientName.matches(".*\\d.*")) {
+            throw new CustomException("患者姓名不能包含数字", 400);
+        }
+
+        if (!patientName.matches("^[\\u4e00-\\u9fa5a-zA-Z\\s]+$")) {
+            throw new CustomException("患者姓名只能包含中文、英文和空格", 400);
+        }
+    }
+
+    /**
+     * 校验患者电话
+     */
+    private void validatePhone(String phone) {
+        if (StringUtils.isEmpty(phone)) {
+            throw new CustomException("患者联系电话不能为空", 400);
+        }
+
+        // 支持手机号或固话
+        if (!phone.matches("^1[3-9]\\d{9}$") && !phone.matches("^\\d{3,4}-?\\d{7,8}$")) {
+            throw new CustomException("患者联系电话格式不正确,请输入正确的手机号或固定电话", 400);
+        }
+    }
+
+    /**
+     * 校验患者出生日期
+     */
+    private void validatePatientBirthday(String birthday) {
+        if (StringUtils.isBlank(birthday)) {
+            throw new CustomException("患者出生日期不能为空", 400);
+        }
+
+        // 验证日期格式 yyyy-MM-dd
+        if (!birthday.matches("^\\d{4}-\\d{2}-\\d{2}$")) {
+            throw new CustomException("患者出生日期格式不正确,应为yyyy-MM-dd", 400);
+        }
+
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+            sdf.setLenient(false); // 设置为严格模式
+            Date date = sdf.parse(birthday);
+
+            // 验证日期不能晚于当前日期
+            Date now = new Date();
+            if (date.after(now)) {
+                throw new CustomException("患者出生日期不能晚于当前日期", 400);
+            }
+
+            // 验证日期不能早于1900年(可根据实际需求调整)
+            Calendar minDate = Calendar.getInstance();
+            minDate.set(1900, Calendar.JANUARY, 1);
+            if (date.before(minDate.getTime())) {
+                throw new CustomException("患者出生日期不能早于1900-01-01", 400);
+            }
+
+            // 计算年龄
+            Calendar birthCal = Calendar.getInstance();
+            birthCal.setTime(date);
+
+            Calendar nowCal = Calendar.getInstance();
+            nowCal.setTime(now);
+
+            int age = nowCal.get(Calendar.YEAR) - birthCal.get(Calendar.YEAR);
+
+            // 如果今年还没过生日,年龄减1
+            if (nowCal.get(Calendar.MONTH) < birthCal.get(Calendar.MONTH) ||
+                    (nowCal.get(Calendar.MONTH) == birthCal.get(Calendar.MONTH) &&
+                            nowCal.get(Calendar.DAY_OF_MONTH) < birthCal.get(Calendar.DAY_OF_MONTH))) {
+                age--;
+            }
+
+            // 验证年龄必须大于等于18岁
+            if (age < 18) {
+                throw new CustomException("患者年龄必须大于等于18岁", 400);
+            }
+
+            // 可选:验证年龄范围(最大年龄不超过150岁)
+            if (age > 150) {
+                throw new CustomException("患者年龄不能超过150岁", 400);
+            }
+
+        } catch (ParseException e) {
+            throw new CustomException("患者出生日期格式无效,请输入有效的日期", 400);
+        }
+    }
+
+    /**
+     * 校验患者性别
+     */
+    private void validatePatientGender(String gender) {
+        if (StringUtils.isBlank(gender)) {
+            throw new CustomException("患者性别不能为空", 400);
+        }
+        // 验证性别值(根据您的注释:1:男 2:⼥)
+        if (!"1".equals(gender) && !"2".equals(gender)) {
+            throw new CustomException("患者性别值不正确,应为1(男)或2(女)", 400);
+        }
+    }
+
+    /**
+     * 校验患者体重
+     */
+    private void validatePatientWeight(String weight) {
+        if (StringUtils.isBlank(weight)) {
+            throw new CustomException("患者体重不能为空", 400);
+        }
+
+        // 验证体重格式(支持整数和小数)
+        if (!weight.matches("^\\d+(\\.\\d{1,2})?$")) {
+            throw new CustomException("患者体重格式不正确,应为数字(可包含最多两位小数)", 400);
+        }
+
+        try {
+            double weightValue = Double.parseDouble(weight);
+
+            // 验证体重范围(单位:公斤)
+            if (weightValue <= 0) {
+                throw new CustomException("患者体重必须大于0", 400);
+            }
+
+            if (weightValue < 1.0) {
+                throw new CustomException("患者体重不能小于1公斤", 400);
+            }
+
+            if (weightValue > 300.0) {
+                throw new CustomException("患者体重不能超过300公斤", 400);
+            }
+
+            // 验证小数点后位数(如果上面正则已经限制,这里可以不做)
+            if (weight.contains(".") && weight.split("\\.")[1].length() > 2) {
+                throw new CustomException("患者体重最多支持两位小数", 400);
+            }
+
+            // 可选:根据年龄范围验证体重是否合理(需要年龄参数)
+            // validateWeightByAge(weightValue, age);
+
+        } catch (NumberFormatException e) {
+            throw new CustomException("患者体重格式不正确,请输入有效的数字", 400);
+        }
+    }
 }

+ 4 - 1
fs-service/src/main/java/com/fs/his/vo/FsPrescribeListVO.java

@@ -181,7 +181,10 @@ public class FsPrescribeListVO {
     /** 组别 */
     private String groupCode;
 
-    //处方来源 暂定 1:信息采集表
+    /**
+     * 处方来源
+     * 1:信息采集表 2:超拼网
+     * */
     private Integer prescribeSource;
 
 }

+ 5 - 0
fs-service/src/main/java/com/fs/his/vo/FsPrescribeVO.java

@@ -169,4 +169,9 @@ public class FsPrescribeVO implements Serializable {
     private Date updateTime;
     private String prescribeCodeUrl;
     private String icdCode;
+
+    /**
+     * 第三方用户id 暂时只有超拼网用户id
+     * */
+    private Long thirdPartyUserId;
 }

+ 2 - 3
fs-service/src/main/java/com/fs/his/vo/SubmitUserInformationVO.java

@@ -10,11 +10,10 @@ import java.io.Serializable;
 public class SubmitUserInformationVO implements Serializable {
     
     private static final long serialVersionUID = 1L;
-    
     /**
-     * 用户ID
+     * 患者id
      */
-    private Long userId;
+    private Long patientId;
     
     /**
      * 采集信息ID

+ 5 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsUserInformationCollection.java

@@ -103,4 +103,9 @@ public class FsUserInformationCollection extends BaseEntity{
      * */
     private Integer infoSource;
 
+    /**
+     * 第三方用户id 暂时只有超拼网用户id
+     * */
+    private Long thirdPartyUserId;
+
 }

+ 5 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsUserInformationCollectionMapper.java

@@ -101,4 +101,9 @@ public interface FsUserInformationCollectionMapper extends BaseMapper<FsUserInfo
      * 修复手机号码和性别数据存储问题
      * */
     int resolvePhoneNumberGenderDataStoreIssue();
+
+    /**
+     * 根据第三方用户id和信息来源查询用户信息采集
+     * */
+    FsUserInformationCollection getCollectionByInfoSourceAndThirdPartyUserId(FsUserInformationCollection queryCondition);
 }

+ 8 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsUserInformationCollectionService.java

@@ -90,8 +90,16 @@ public interface IFsUserInformationCollectionService extends IService<FsUserInfo
     //自动退款
     void autoRefund(FsUserInformationCollection collection,Integer operateType);
 
+    //根据用户id查询信息采集详情
     FsUserInformationCollectionVO getCollectionByUserId(Long userId);
 
+    /**
+     * 根据第三方用户id和采集来源查询信息采集详情
+     * @param infoSource 信息来源
+     * @param thirdPartyUserId 第三方用户id
+     * */
+    FsUserInformationCollectionVO getCollectionByInfoSourceAndThirdPartyUserId(Integer infoSource,Long thirdPartyUserId);
+
     //1-终止 2-拒方
     R stopCollection(FsUserInformationCollectionSchedule param, Long operateId,Integer operateType);
 

+ 19 - 6
fs-service/src/main/java/com/fs/hisStore/service/IOpenApiCPWUserInformationService.java

@@ -15,17 +15,30 @@ public interface IOpenApiCPWUserInformationService {
     public FsQuestionAndAnswerVO getInformationTemplate();
 
     /**
-     * 创建用户信息与地址--超拼网
+     * 提交患者待开方信息--超拼网
      * */
-    public SubmitUserInformationVO createUserInformationCPW(CPWUserAndAddressAddDTO cpwUserAndAddressAddDTO);
+    SubmitUserInformationVO submitPatientPrescriptionInfo(CPWUserInfoCollectionAddDTO collectionAddDTO);
 
     /**
-     * 提交用户采集信息--超拼网
+     * 推送处方信息给第三方(超拼网)
      * */
-    public SubmitUserInformationVO submitUserInformation(CPWUserInfoCollectionAddDTO collectionAddDTO);
+    public Object pushPrescription(String userId, String prescriptionId);
+
+
+
+
 
     /**
-     * 推送处方信息给第三方
+     * 创建用户信息与地址--超拼网----------已弃用!!!!
      * */
-    public Object pushPrescription(String userId, String prescriptionId);
+    public SubmitUserInformationVO createUserInformationCPW(CPWUserAndAddressAddDTO cpwUserAndAddressAddDTO);
+
+    /**
+     * 提交用户采集信息--超拼网----------已弃用!!!!
+     * */
+    public SubmitUserInformationVO submitUserInformation(CPWUserInfoCollectionAddDTO collectionAddDTO);
+
+
+
+
 }

+ 18 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsUserInformationCollectionServiceImpl.java

@@ -1031,6 +1031,24 @@ public class FsUserInformationCollectionServiceImpl extends ServiceImpl<FsUserIn
         return vo;
     }
 
+    @Override
+    public FsUserInformationCollectionVO getCollectionByInfoSourceAndThirdPartyUserId(Integer infoSource,Long thirdPartyUserId) {
+        FsUserInformationCollectionVO vo = new FsUserInformationCollectionVO();
+        FsUserInformationCollection queryCondition=new FsUserInformationCollection();
+        queryCondition.setThirdPartyUserId(thirdPartyUserId);
+        queryCondition.setInfoSource(infoSource);
+        FsUserInformationCollection collection = fsUserInformationCollectionMapper.getCollectionByInfoSourceAndThirdPartyUserId(queryCondition);
+        if (collection != null) {
+            BeanUtils.copyProperties(collection, vo);
+            vo.setAnswers(JSON.parseArray(collection.getJsonInfo(), AnswerVO.class));
+            //医生建议
+            if (collection.getDoctorAdvice() != null){
+                vo.setDoctorAdvice(collection.getDoctorAdvice());
+            }
+        }
+        return vo;
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public R stopCollection(FsUserInformationCollectionSchedule param, Long operateId,Integer operateType) {

+ 203 - 9
fs-service/src/main/java/com/fs/hisStore/service/impl/OpenApiCPWUserInformationServiceImpl.java

@@ -1,17 +1,22 @@
 package com.fs.hisStore.service.impl;
 
+import cn.hutool.core.util.IdUtil;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
+import com.fs.his.domain.FsDoctor;
+import com.fs.his.domain.FsPrescribe;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUserAddress;
-import com.fs.his.dto.CPWUserAndAddressAddDTO;
-import com.fs.his.dto.CPWUserBaseInfoAddDTO;
-import com.fs.his.dto.CPWUserInfoCollectionAddDTO;
+import com.fs.his.dto.*;
+import com.fs.his.mapper.FsDoctorMapper;
+import com.fs.his.mapper.FsPrescribeMapper;
 import com.fs.his.mapper.FsUserAddressMapper;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.service.IFsQuestionAndAnswerService;
+import com.fs.his.service.IPollingAssignDoctorService;
 import com.fs.his.utils.IdCardUtil;
+import com.fs.his.vo.FsPrescribeListVO;
 import com.fs.his.vo.FsQuestionAndAnswerVO;
 import com.fs.his.vo.SubmitUserInformationVO;
 import com.fs.hisStore.domain.FsUserInformationCollection;
@@ -19,14 +24,18 @@ import com.fs.hisStore.mapper.FsUserInformationCollectionMapper;
 import com.fs.hisStore.service.IOpenApiCPWUserInformationService;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
 import java.util.List;
 
-import static com.fs.his.utils.PhoneUtil.decryptPhone;
-
 /**
  * 对接-超拼网-信息采集服务实现类
  * */
@@ -49,6 +58,15 @@ public class OpenApiCPWUserInformationServiceImpl implements IOpenApiCPWUserInfo
     @Autowired
     private FsUserInformationCollectionMapper fsUserInformationCollectionMapper;
 
+    @Autowired
+    private FsPrescribeMapper prescribeMapper;
+
+    @Autowired
+    private FsDoctorMapper doctorMapper;
+
+    @Autowired
+    private IPollingAssignDoctorService pollingAssignDoctorService;
+
     @Override
     public FsQuestionAndAnswerVO getInformationTemplate() {
         try {
@@ -64,6 +82,106 @@ public class OpenApiCPWUserInformationServiceImpl implements IOpenApiCPWUserInfo
         }
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public SubmitUserInformationVO submitPatientPrescriptionInfo(CPWUserInfoCollectionAddDTO collectionAddDTO) {
+        Long patientId = collectionAddDTO.getPatientInfo().getPatientId();
+        //查询此用户最新的开方进度是否已完成,已完成才能新增待开处方
+        FsPrescribeListVO fsPrescribeListVO = prescribeMapper.selectFsPrescribeListVOByThirdUserId(patientId);
+        if (fsPrescribeListVO!=null){
+            if (fsPrescribeListVO.getStatus() == 0 && StringUtils.isBlank(fsPrescribeListVO.getPrescribeImgUrl())) {
+                log.error("超拼网用户[{}]重复提交待开处方,当前状态:待医生开方", patientId);
+                throw new CustomException("您已提交待开方申请,请等待医生开方处理,请勿重复提交", 400);
+            } else if (fsPrescribeListVO.getStatus() == 0 && StringUtils.isNotBlank(fsPrescribeListVO.getPrescribeImgUrl())) {
+                log.error("超拼网用户[{}]处方处于待审核状态,处方ID:{}", patientId, fsPrescribeListVO.getPrescribeId());
+                throw new CustomException("您的处方正在审核中,请耐心等待药师审核结果", 400);
+            } else if (fsPrescribeListVO.getStatus() == 2) {
+                log.error("超拼网用户[{}]处方药师审核未通过,处方ID:{}", patientId, fsPrescribeListVO.getPrescribeId());
+                throw new CustomException("您的处方药师审核未通过,待医生重新开方中", 400);
+            }
+        }
+        //新增fs_user_information_collection用户信息采集信息
+        FsUserInformationCollection queryFsUserInformationCollection=new FsUserInformationCollection();
+        queryFsUserInformationCollection.setThirdPartyUserId(patientId);
+        queryFsUserInformationCollection.setInfoSource(2);//采集信息来源:超拼网用户
+        FsUserInformationCollection fsUserInformationCollection = transformCollectionDtoToFsUserCollection(collectionAddDTO);
+        List<FsUserInformationCollection> fsUserInformationCollections = fsUserInformationCollectionMapper.selectFsUserInformationCollectionList(queryFsUserInformationCollection);
+        if (CollectionUtils.isEmpty(fsUserInformationCollections)){
+            int addInfoResult = fsUserInformationCollectionMapper.insertFsUserInformationCollection(fsUserInformationCollection);
+            if (addInfoResult <= 0) {
+                throw new CustomException("患者采集信息保存失败", 500);
+            }
+        }else {
+            //存在则更新
+            fsUserInformationCollection.setId(fsUserInformationCollections.get(0).getId());
+            fsUserInformationCollectionMapper.updateFsUserInformationCollection(fsUserInformationCollection);
+        }
+        //新增fs_prescribe处方信息
+        FsPrescribe fsPrescribe= transformCollectionDtoToFsPrescribe(collectionAddDTO,fsUserInformationCollection);
+        int addPrescribeResult = prescribeMapper.insertFsPrescribe(fsPrescribe);
+        if (addPrescribeResult <= 0) {
+            throw new CustomException("患者处方信息保存失败", 500);
+        }
+        //构建成功返回结果
+        SubmitUserInformationVO resultVO = new SubmitUserInformationVO();
+        resultVO.setPatientId(fsUserInformationCollection.getThirdPartyUserId());
+        resultVO.setCollectionId(fsUserInformationCollection.getId());
+        resultVO.setStatus(BigDecimal.ONE.intValue());
+        resultVO.setMessage("提交成功");
+        resultVO.setSubmitTime(DateUtils.getTime());
+        log.info("超拼网---用户待开方信息提交成功,patientName: {}, collectionId: {}",
+                collectionAddDTO.getPatientInfo().getPatientName(), fsUserInformationCollection.getId());
+        return resultVO;
+    }
+
+    private FsPrescribe transformCollectionDtoToFsPrescribe(CPWUserInfoCollectionAddDTO collectionAddDTO,FsUserInformationCollection fsUserInformationCollection) {
+        CPWPatientInfoAddDTO patientInfo = collectionAddDTO.getPatientInfo();
+        FsPrescribe fsPrescribe=new FsPrescribe();
+        fsPrescribe.setThirdPartyUserId(collectionAddDTO.getPatientInfo().getPatientId());
+        fsPrescribe.setCreateTime(DateUtils.getNowDate());
+        fsPrescribe.setStatus(0);//待开方
+        fsPrescribe.setDoctorConfirm(0);//未确认
+        //fsPrescribe.setUsageJson(JSON.toJSONString(collectionAddDTO.getAnswers()));
+        fsPrescribe.setHistoryAllergic(collectionAddDTO.getAllergy());
+        String prescribeCode= IdUtil.getSnowflake(0, 0).nextIdStr();
+        fsPrescribe.setPrescribeCode(prescribeCode);
+        fsPrescribe.setPatientAge(String.valueOf(fsUserInformationCollection.getAge()));
+        fsPrescribe.setPatientId(patientInfo.getPatientId());
+        fsPrescribe.setPatientName(patientInfo.getPatientName());
+        fsPrescribe.setPatientTel(patientInfo.getPatientTel());
+        fsPrescribe.setPatientGender(patientInfo.getPatientGender());
+        fsPrescribe.setPatientBirthday(patientInfo.getPatientBirthday());
+        fsPrescribe.setWeight(patientInfo.getWeight());
+        Long doctorId = 437L;
+        fsPrescribe.setDoctorId(doctorId);
+        FsDoctor fsDoctor = doctorMapper.selectFsDoctorByDoctorId(doctorId);
+        if (fsDoctor==null){
+            throw new CustomException("开方医生信息获取异常:"+437L);
+        }
+
+        fsPrescribe.setPrescribeDoctorId(fsDoctor.getDoctorId());
+        fsPrescribe.setPrescribeDoctorSignUrl(fsDoctor.getSignUrl());
+
+        //分配在线的随机药师
+        fsPrescribe.setDrugDoctorId(515L);
+        fsPrescribe.setDrugDoctorSignUrl("https://ysy-1329817240.cos.ap-guangzhou.myqcloud.com/ysy/20251114/7a0d28df6aa94a9c819e025c9a230c6f.png");
+//        FsDoctor fsDrugDoctor = null;
+//        try {
+//            // 使用新写的轮询服务分配药师,传入处方编号
+//            fsDrugDoctor = pollingAssignDoctorService.getNextPharmacist(prescribeCode);
+//
+//            fsPrescribe.setDrugDoctorId(fsDrugDoctor.getDoctorId());
+//            fsPrescribe.setDrugDoctorSignUrl(fsDrugDoctor.getSignUrl());
+//
+//            log.info("超拼网轮询分配药师成功 - 处方号:{}, 药师ID:{}, 药师姓名:{}",
+//                    prescribeCode, fsDrugDoctor.getDoctorId(), fsDrugDoctor.getDoctorName());
+//        } catch (Exception e) {
+//            log.error("超拼网患者-分配药师失败 - 处方号:{}", prescribeCode, e);
+//            fsPrescribe.setDrugDoctorId(1L);//为了不影响主业务执行设置默认值
+//        }
+        return fsPrescribe;
+    }
+
     @Override
     public SubmitUserInformationVO createUserInformationCPW(CPWUserAndAddressAddDTO cpwUserAndAddressAddDTO) {
         SubmitUserInformationVO resultVO=new SubmitUserInformationVO();
@@ -86,7 +204,6 @@ public class OpenApiCPWUserInformationServiceImpl implements IOpenApiCPWUserInfo
         if (addResult2 <= 0) {
             throw new CustomException("用户地址信息保存失败", 500);
         }
-        resultVO.setUserId(addFsUser.getUserId());
         resultVO.setStatus(BigDecimal.ONE.intValue());
         resultVO.setMessage("提交成功");
         resultVO.setSubmitTime(DateUtils.getTime());
@@ -97,7 +214,7 @@ public class OpenApiCPWUserInformationServiceImpl implements IOpenApiCPWUserInfo
     //@Transactional(rollbackFor = Exception.class)
     public SubmitUserInformationVO submitUserInformation(CPWUserInfoCollectionAddDTO collectionAddDTO) {
         //新增fs_user_information_collection用户采集信息
-        Long userId = collectionAddDTO.getUserId();
+        Long userId = collectionAddDTO.getPatientInfo().getPatientId();
         FsUser fsUserQueryCondition=new FsUser();
         fsUserQueryCondition.setUserId(userId);
         fsUserQueryCondition.setUserSource(2);//超拼网用户
@@ -113,7 +230,6 @@ public class OpenApiCPWUserInformationServiceImpl implements IOpenApiCPWUserInfo
         }
         //构建成功返回结果
         SubmitUserInformationVO resultVO = new SubmitUserInformationVO();
-        resultVO.setUserId(fsUserInformationCollection.getUserId());
         resultVO.setCollectionId(fsUserInformationCollection.getId());
         resultVO.setStatus(BigDecimal.ONE.intValue());
         resultVO.setMessage("提交成功");
@@ -128,6 +244,28 @@ public class OpenApiCPWUserInformationServiceImpl implements IOpenApiCPWUserInfo
         return null;
     }
 
+    /**
+     * 转换用户采集信息fs_user_information_collection
+     * */
+    private FsUserInformationCollection transformCollectionDtoToFsUserCollection(CPWUserInfoCollectionAddDTO collectionAddDTO) {
+        CPWPatientInfoAddDTO patientInfo = collectionAddDTO.getPatientInfo();
+        FsUserInformationCollection fsUserInformationCollection=new FsUserInformationCollection();
+        fsUserInformationCollection.setThirdPartyUserId(collectionAddDTO.getPatientInfo().getPatientId());
+        fsUserInformationCollection.setQuestionId(QUESTION_ID);
+        fsUserInformationCollection.setUserName(collectionAddDTO.getPatientInfo().getPatientName());
+        fsUserInformationCollection.setUserPhoneFour(patientInfo.getPatientTel().substring(patientInfo.getPatientTel().length()-4));//手机号后四位
+        fsUserInformationCollection.setSex(convertGenderToLong(patientInfo.getPatientGender()));
+        fsUserInformationCollection.setAge(calculateAge(patientInfo.getPatientBirthday()));//年龄
+        fsUserInformationCollection.setAllergy(collectionAddDTO.getAllergy());
+        fsUserInformationCollection.setRemark(collectionAddDTO.getRemark());
+        fsUserInformationCollection.setInfoSource(2);//采集信息来源:超拼网用户
+        fsUserInformationCollection.setCreateTime(DateUtils.getNowDate());
+        fsUserInformationCollection.setUserConfirm(BigDecimal.ZERO.intValue());//用户第一次确认 0-未确认 1-已确认
+        fsUserInformationCollection.setUserConfirm2(BigDecimal.ZERO.intValue());//用户第二次确认 0-未确认 1-已确认
+        fsUserInformationCollection.setDoctorConfirm(BigDecimal.ZERO.intValue());//医生确认开方 0-未确认 1-已确认
+        fsUserInformationCollection.setJsonInfo(JSON.toJSONString(collectionAddDTO.getAnswers()));//采集信息
+        return fsUserInformationCollection;
+    }
     /**
      * 转换用户信息fs_user
      * */
@@ -162,7 +300,7 @@ public class OpenApiCPWUserInformationServiceImpl implements IOpenApiCPWUserInfo
      * */
     private FsUserInformationCollection transformUserDtoToFsUserCollection(CPWUserInfoCollectionAddDTO collectionAddDTO,FsUser fsUser) {
         FsUserInformationCollection fsUserInformationCollection=new FsUserInformationCollection();
-        fsUserInformationCollection.setUserId(collectionAddDTO.getUserId());
+        fsUserInformationCollection.setThirdPartyUserId(collectionAddDTO.getPatientInfo().getPatientId());
         fsUserInformationCollection.setQuestionId(QUESTION_ID);
         fsUserInformationCollection.setUserName(fsUser.getRealName());
         fsUserInformationCollection.setUserPhoneFour(fsUser.getPhone().substring(fsUser.getPhone().length()-4));//手机号后四位
@@ -178,4 +316,60 @@ public class OpenApiCPWUserInformationServiceImpl implements IOpenApiCPWUserInfo
         fsUserInformationCollection.setJsonInfo(JSON.toJSONString(collectionAddDTO.getAnswers()));//采集信息
         return fsUserInformationCollection;
     }
+
+    /**
+     * 根据出生日期计算年龄
+     * @param birthday 出生日期,格式:yyyy-MM-dd
+     * @return Integer类型的年龄,如果入参无效返回null
+     */
+    public static Integer calculateAge(String birthday) {
+        if (StringUtils.isBlank(birthday)) {
+            return null;
+        }
+
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+            sdf.setLenient(false);
+            Date birthDate = sdf.parse(birthday);
+
+            Calendar birthCal = Calendar.getInstance();
+            birthCal.setTime(birthDate);
+
+            Calendar nowCal = Calendar.getInstance();
+
+            int age = nowCal.get(Calendar.YEAR) - birthCal.get(Calendar.YEAR);
+
+            // 如果今年还没过生日,年龄减1
+            if (nowCal.get(Calendar.MONTH) < birthCal.get(Calendar.MONTH) ||
+                    (nowCal.get(Calendar.MONTH) == birthCal.get(Calendar.MONTH) &&
+                            nowCal.get(Calendar.DAY_OF_MONTH) < birthCal.get(Calendar.DAY_OF_MONTH))) {
+                age--;
+            }
+
+            return age;
+
+        } catch (ParseException e) {
+            throw new CustomException("用户年龄计算异常", 500);
+        }
+    }
+
+    /**
+     * 将性别字符转换为Long类型
+     * @param gender 性别字符:"1"表示男,"2"表示女
+     * @return "1"返回1L,"2"返回0L,其他情况返回null
+     */
+    public static Long convertGenderToLong(String gender) {
+        if (StringUtils.isBlank(gender)) {
+            return null;
+        }
+
+        switch (gender) {
+            case "1":
+                return 1L;
+            case "2":
+                return 0L;
+            default:
+                throw new CustomException("用户性别数入异常", 500);
+        }
+    }
 }

+ 62 - 33
fs-service/src/main/resources/mapper/his/FsPrescribeMapper.xml

@@ -52,6 +52,8 @@
         <result property="startOperateTime"    column="start_operate_time"    />
         <result property="endOperateTime"    column="end_operate_time"    />
         <result property="operateSecond"    column="operate_second"    />
+        <result property="thirdPartyUserId"    column="third_party_user_id"    />
+        <result property="isSendToThirdParty"    column="is_send_to_third_party"    />
     </resultMap>
 
     <sql id="selectFsPrescribeVo">
@@ -63,7 +65,7 @@
              , patient_age, patient_name, weight, is_history_allergic, history_allergic, liver_unusual
              , renal_unusual, is_lactation, patient_tel, patient_gender, record_pic, prescribe_img_url
              , audit_reason, diagnose, doctor_id,drug_doctor_id, create_time, status, audit_time,remark
-             ,usage_json,store_id,doctor_confirm, start_operate_time,end_operate_time,operate_second from fs_prescribe
+             ,usage_json,store_id,doctor_confirm, start_operate_time,end_operate_time,operate_second,third_party_user_id,is_send_to_third_party from fs_prescribe
     </sql>
 
     <select id="selectFsPrescribeList" parameterType="FsPrescribe" resultMap="FsPrescribeResult">
@@ -96,6 +98,8 @@
             <if test="status != null "> and status = #{status}</if>
             <if test="auditTime != null "> and audit_time = #{auditTime}</if>
             <if test="doctorConfirm != null "> and doctor_confirm = #{doctorConfirm}</if>
+            <if test="thirdPartyUserId != null "> and third_party_user_id = #{thirdPartyUserId}</if>
+            <if test="isSendToThirdParty != null "> and is_send_to_third_party = #{isSendToThirdParty}</if>
         </where>
         order by prescribe_id desc
     </select>
@@ -154,6 +158,8 @@
             <if test="startOperateTime != null">start_operate_time,</if>
             <if test="endOperateTime != null">end_operate_time,</if>
             <if test="operateSecond != null">operate_second,</if>
+            <if test="thirdPartyUserId != null">third_party_user_id,</if>
+            <if test="isSendToThirdParty != null">is_send_to_third_party,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="prescribeType != null">#{prescribeType},</if>
@@ -202,6 +208,8 @@
             <if test="startOperateTime != null">#{startOperateTime},</if>
             <if test="endOperateTime != null">#{endOperateTime},</if>
             <if test="operateSecond != null">#{operateSecond},</if>
+            <if test="thirdPartyUserId != null">#{thirdPartyUserId},</if>
+            <if test="isSendToThirdParty != null">#{isSendToThirdParty},</if>
         </trim>
     </insert>
 
@@ -254,6 +262,8 @@
             <if test="startOperateTime != null">start_operate_time = #{startOperateTime},</if>
             <if test="endOperateTime != null">end_operate_time = #{endOperateTime},</if>
             <if test="operateSecond != null">operate_second = #{operateSecond},</if>
+            <if test="thirdPartyUserId != null">third_party_user_id = #{thirdPartyUserId},</if>
+            <if test="isSendToThirdParty != null">is_send_to_third_party = #{isSendToThirdParty},</if>
         </trim>
         where prescribe_id = #{prescribeId}
     </update>
@@ -295,43 +305,56 @@
         <if test="orderStatus != null "> and fso.`status` = #{orderStatus}</if>
         <if test="storeId != null "> and so.store_id = #{storeId}</if>
     </select>
-    <!--    已取消、已退款的处方不展示-->
+
     <select id="selectFsPrescribeListVOWithConfirm" resultType="com.fs.his.vo.FsPrescribeListVO">
         select so.*,CAST(operate_second AS CHAR) operateTime,us.nick_name,dc.doctor_name,
-               dp.doctor_name doctor_drug_name,
-               fso.order_code,fso.`status` order_status,
-               fse.store_name FROM fs_prescribe so
-                   LEFT JOIN fs_user us ON us.user_id=so.user_id
-                   LEFT JOIN fs_doctor dc ON dc.doctor_id = so.doctor_id
-                   LEFT JOIN fs_doctor dp ON dp.doctor_id =so.drug_doctor_id
-                   INNER JOIN fs_store_order fso ON fso.order_id = so.store_order_id AND fso.status != -3  AND fso.status != -2
-                   LEFT JOIN fs_store fse ON fse.store_id = so.store_id
+        dp.doctor_name doctor_drug_name,
+        fso.order_code,fso.`status` order_status,
+        fse.store_name,
+        CASE
+        WHEN uics.order_code IS NOT NULL THEN 1         <!--  处方来源"信息采集进度表"-->
+        WHEN so.third_party_user_id IS NOT NULL AND infoc.info_source = 2 THEN 2        <!--  处方来源"超拼网"-->
+        ELSE NULL                                       <!--  处方来源"其他"-->
+        END as prescribeSource
+        FROM fs_prescribe so
+        LEFT JOIN fs_user us ON us.user_id=so.user_id
+        LEFT JOIN fs_doctor dc ON dc.doctor_id = so.doctor_id
+        LEFT JOIN fs_doctor dp ON dp.doctor_id =so.drug_doctor_id
+        LEFT JOIN fs_store_order fso ON fso.order_id = so.store_order_id
+        LEFT JOIN fs_store fse ON fse.store_id = so.store_id
+        LEFT JOIN fs_user_information_collection_schedule uics ON uics.order_code = fso.order_code
+        LEFT JOIN fs_user_information_collection infoc ON infoc.third_party_user_id = so.third_party_user_id
         <where>
-            doctor_confirm !=  -1
-            <if test="companyId != null "> and fso.company_id = #{companyId}</if>
-            <if test="companyUserId != null "> and fso.company_user_id = #{companyUserId}</if>
-            <if test="prescribeType != null "> and so.prescribe_type = #{prescribeType}</if>
-            <if test="inquiryOrderId != null "> and so.inquiry_order_id = #{inquiryOrderId}</if>
-            <if test="storeOrderId != null "> and so.store_order_id = #{storeOrderId}</if>
-            <if test="userId != null "> and so.user_id = #{userId}</if>
-            <if test="prescribeCode != null  and prescribeCode != ''"> and so.prescribe_code = #{prescribeCode}</if>
-            <if test="patientName != null  and patientName != ''"> and so.patient_name like concat('%', #{patientName}, '%')</if>
-            <if test="doctorName != null  and doctorName != ''"> and dc.doctor_name like concat('%', #{doctorName}, '%')</if>
-            <if test="patientTel != null  and patientTel != ''"> and so.patient_tel = #{patientTel}</if>
-            <if test="doctorId != null  and doctorId != ''"> and so.doctor_id = #{doctorId}</if>
-            <if test="status != null "> and so.status = #{status}</if>
-            <if test="sTime != null ">  and DATE(so.create_time) &gt;= DATE(#{sTime})</if>
-            <if test="eTime != null ">  and DATE(so.create_time) &lt;= DATE(#{eTime})</if>
-            <if test="auditSTime != null ">  and DATE(so.audit_time) &gt;= DATE(#{auditSTime})</if>
-            <if test="auditETime != null ">  and DATE(so.audit_time) &lt;= DATE(#{auditETime})</if>
-            <if test="auditTime != null "> and so.audit_time = #{auditTime}</if>
-            <if test="orderCode != null  and orderCode != ''"> and fso.order_code = #{orderCode}</if>
-            <if test="orderStatus != null "> and fso.`status` = #{orderStatus}</if>
-            <if test="storeId != null "> and so.store_id = #{storeId}</if>
+            so.doctor_confirm != -1                <!--  被医生拒访的处方不展示-->
+            AND (fso.order_id IS NULL OR (fso.status != -3 AND fso.status != -2))   <!--  已取消与已退款的订单处方不展示-->
+            <if test="companyId != null ">and fso.company_id = #{companyId}</if>
+            <if test="companyUserId != null ">and fso.company_user_id = #{companyUserId}</if>
+            <if test="prescribeType != null ">and so.prescribe_type = #{prescribeType}</if>
+            <if test="inquiryOrderId != null ">and so.inquiry_order_id = #{inquiryOrderId}</if>
+            <if test="storeOrderId != null ">and so.store_order_id = #{storeOrderId}</if>
+            <if test="userId != null ">and so.user_id = #{userId}</if>
+            <if test="prescribeCode != null  and prescribeCode != ''">and so.prescribe_code = #{prescribeCode}</if>
+            <if test="patientName != null  and patientName != ''">and so.patient_name like concat('%', #{patientName},
+                '%')
+            </if>
+            <if test="doctorName != null  and doctorName != ''">and dc.doctor_name like concat('%', #{doctorName},
+                '%')
+            </if>
+            <if test="patientTel != null  and patientTel != ''">and so.patient_tel = #{patientTel}</if>
+            <if test="doctorId != null  and doctorId != ''">and so.doctor_id = #{doctorId}</if>
+            <if test="status != null ">and so.status = #{status}</if>
+            <if test="sTime != null ">and DATE(so.create_time) &gt;= DATE(#{sTime})</if>
+            <if test="eTime != null ">and DATE(so.create_time) &lt;= DATE(#{eTime})</if>
+            <if test="auditSTime != null ">and DATE(so.audit_time) &gt;= DATE(#{auditSTime})</if>
+            <if test="auditETime != null ">and DATE(so.audit_time) &lt;= DATE(#{auditETime})</if>
+            <if test="auditTime != null ">and so.audit_time = #{auditTime}</if>
+            <if test="orderCode != null  and orderCode != ''">and fso.order_code = #{orderCode}</if>
+            <if test="orderStatus != null ">and fso.`status` = #{orderStatus}</if>
+            <if test="storeId != null ">and so.store_id = #{storeId}</if>
         </where>
         order by
-            CASE WHEN doctor_confirm = 0 THEN 0 ELSE 1 END,
-            prescribe_id desc
+        CASE WHEN so.doctor_confirm = 0 THEN 0 ELSE 1 END,
+        so.prescribe_id desc
     </select>
     <select id="selectDoctorOperateStatList" resultType="com.fs.his.vo.DoctorOperateStatVO">
         SELECT
@@ -411,4 +434,10 @@
             <otherwise>ASC</otherwise>
         </choose>
     </select>
+    <select id="selectFsPrescribeListVOByThirdUserId" resultType="com.fs.his.vo.FsPrescribeListVO">
+        select *
+        from fs_prescribe
+        where third_party_user_id = #{thirdPartyUserId}
+          and (status = 0 or status = 2) <!--  医师、药师审核中,以及药师审核不通过的状态 -->
+    </select>
 </mapper>

+ 10 - 1
fs-service/src/main/resources/mapper/hisStore/FsUserInformationCollectionMapper.xml

@@ -32,6 +32,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="remark"    column="remark"    />
         <result property="age"    column="age"    />
         <result property="infoSource"    column="info_source"    />
+        <result property="thirdPartyUserId"    column="third_party_user_id"    />
     </resultMap>
 
     <sql id="selectFsUserInformationCollectionVo">
@@ -39,7 +40,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              , doctor_confirm, create_time, update_time,doctor_id,company_user_id
              ,package_id,pay_type,amount,is_package,user_confirm2,package_order_code
              ,status,user_advice,doctor_advice,doctor_confirm_time,sex,user_name,user_phone_four
-             ,allergy,remark,age,info_source  from fs_user_information_collection
+             ,allergy,remark,age,info_source,third_party_user_id  from fs_user_information_collection
     </sql>
 
     <select id="selectFsUserInformationCollectionList" parameterType="FsUserInformationCollection" resultMap="FsUserInformationCollectionResult">
@@ -52,6 +53,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="doctorConfirm != null "> and doctor_confirm = #{doctorConfirm}</if>
             <if test="packageOrderCode != null "> and package_order_code = #{packageOrderCode}</if>
             <if test="infoSource != null "> and info_source = #{infoSource}</if>
+            <if test="thirdPartyUserId != null "> and third_party_user_id = #{thirdPartyUserId}</if>
         </where>
     </select>
     
@@ -84,6 +86,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{userId}
         </foreach>
     </select>
+    <select id="getCollectionByInfoSourceAndThirdPartyUserId" resultMap="FsUserInformationCollectionResult">
+        <include refid="selectFsUserInformationCollectionVo"/>
+        where info_source = #{infoSource} and third_party_user_id = #{thirdPartyUserId}
+    </select>
 
     <insert id="insertFsUserInformationCollection" parameterType="FsUserInformationCollection" useGeneratedKeys="true" keyProperty="id">
         insert into fs_user_information_collection
@@ -114,6 +120,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="remark != null">remark,</if>
             <if test="age != null">age,</if>
             <if test="infoSource != null">info_source,</if>
+            <if test="thirdPartyUserId != null">third_party_user_id,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="questionId != null">#{questionId},</if>
@@ -142,6 +149,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="remark != null">#{remark},</if>
             <if test="age != null">#{age},</if>
             <if test="infoSource != null">#{infoSource},</if>
+            <if test="thirdPartyUserId != null">#{thirdPartyUserId},</if>
          </trim>
     </insert>
 
@@ -174,6 +182,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="remark != null">remark = #{remark},</if>
             <if test="age != null">age = #{age},</if>
             <if test="infoSource != null">info_source = #{infoSource},</if>
+            <if test="thirdPartyUserId != null">third_party_user_id = #{thirdPartyUserId},</if>
         </trim>
         where id = #{id}
     </update>