| | |
| | | import com.ruoyi.common.utils.RSAPublicKeyExample; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.smartor.domain.*; |
| | | import com.smartor.domain.DTO.ServiceSubtaskDetailDTO; |
| | | import com.smartor.domain.VO.ServiceSubtaskDetailTraceVO; |
| | | import com.smartor.domain.entity.ServiceSubtaskEntity; |
| | | import com.ruoyi.task.domain.VO.ServiceSubtaskDetailTraceVO; |
| | | import com.smartor.mapper.*; |
| | | import com.smartor.service.*; |
| | | import lombok.extern.slf4j.Slf4j; |
| | |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import com.ruoyi.template.domain.HeLibrary; |
| | | import com.ruoyi.template.domain.IvrLibaScriptAssort; |
| | | import com.ruoyi.inhospvisit.domain.PatMedInhosp; |
| | | import com.ruoyi.outpatientvisit.domain.PatMedOuthosp; |
| | | import com.ruoyi.outpatientvisit.domain.PatMedOuthospQueryReq; |
| | | import com.ruoyi.inhospvisit.api.InhospVisitFacade; |
| | | import com.ruoyi.outpatientvisit.api.OuthospDataFacade; |
| | | import com.ruoyi.template.domain.IvrTaskTemplate; |
| | | import com.ruoyi.template.domain.IvrTaskTemplateScriptVO; |
| | | import com.ruoyi.template.domain.IvrTaskTemplateTargetoption; |
| | | import com.ruoyi.template.service.IIvrTaskTemplateService; |
| | | import com.ruoyi.template.domain.IvrLibaTemplateScript; |
| | | import com.ruoyi.template.mapper.IvrLibaScriptAssortMapper; |
| | | import com.ruoyi.template.service.IIvrLibaTemplateScriptService; |
| | | import com.ruoyi.template.domain.SvyLibScriptCategory; |
| | | import com.ruoyi.template.domain.SvyLibTemplateScript; |
| | | import com.ruoyi.template.domain.SvyTaskTemplate; |
| | | import com.ruoyi.template.domain.SvyTaskTemplateScriptVO; |
| | | import com.ruoyi.template.domain.SvyTaskTemplateTargetoption; |
| | | import com.ruoyi.template.mapper.SvyLibScriptCategoryMapper; |
| | | import com.ruoyi.template.service.ISvyTaskTemplateService; |
| | | import com.ruoyi.common.core.domain.entity.PatArchive; |
| | | import com.ruoyi.task.domain.DTO.ServiceSubtaskDetailDTO; |
| | | import com.ruoyi.task.domain.entity.ServiceSubtaskEntity; |
| | | import com.ruoyi.task.domain.ServiceSubtask; |
| | | import com.ruoyi.task.domain.ServiceSubtaskDetail; |
| | | import com.ruoyi.task.domain.ServiceSubtaskDetailVO; |
| | | import com.ruoyi.task.domain.ServiceTask; |
| | | import com.ruoyi.task.domain.TaskSendTimeVO; |
| | | import com.ruoyi.task.mapper.ServiceSubtaskDetailMapper; |
| | | import com.ruoyi.task.mapper.ServiceSubtaskMapper; |
| | | import com.ruoyi.task.domain.ServiceSubtaskDetailTrace; |
| | | import com.ruoyi.task.domain.VO.ServiceSubtaskDetailTraceVO; |
| | | import com.ruoyi.task.service.IServiceSubtaskDetailTraceService; |
| | | import com.ruoyi.task.domain.ServiceSubtaskPreachform; |
| | | import com.ruoyi.task.service.IServiceTaskService; |
| | | /** |
| | | * 任务问卷问题选项Service业务层处理 |
| | | * |
| | |
| | | public class ServiceSubtaskAnswerServiceImpl implements IServiceSubtaskAnswerService { |
| | | @Autowired |
| | | private ServiceSubtaskAnswerMapper serviceSubtaskAnswerMapper; |
| | | |
| | | /** |
| | | * 跨模块访问一律经模块门面(禁止直连已搬走的 Mapper)。 |
| | | */ |
| | | @Autowired |
| | | private OuthospDataFacade outhospDataFacade; |
| | | |
| | | /** |
| | | * 跨模块访问一律经模块门面(禁止直连已搬走的 Mapper)。 |
| | | */ |
| | | @Autowired |
| | | private InhospVisitFacade inhospVisitFacade; |
| | | |
| | | @Autowired |
| | | private ServiceSubtaskMapper serviceSubtaskMapper; |
| | |
| | | private PatArchiveMapper patArchiveMapper; |
| | | |
| | | |
| | | @Autowired |
| | | private PatMedInhospMapper patMedInhospMapper; |
| | | |
| | | @Autowired |
| | | private PatMedOuthospMapper patMedOuthospMapper; |
| | | |
| | | @Autowired |
| | | private IServiceSubtaskDetailTraceService traceService; |
| | |
| | | @Autowired |
| | | private ISvyLibTemplateScriptService svyLibTemplateScriptService; |
| | | |
| | | @Autowired |
| | | private ISvyTaskTemplateTargetoptionService svyTaskTemplateTargetoptionService; |
| | | |
| | | @Autowired |
| | | private IIvrTaskTemplateService ivrTaskTemplateService; |
| | |
| | | public Integer saveQuestionAnswer(ServiceSubTaskAnswerReq serviceSubTaskAnswerReq, Long flag) { |
| | | String threadName = Thread.currentThread().getName(); |
| | | log.info("[{}] 开始异步处理保存问卷答案", threadName); |
| | | // 幂等占位键:失败时要释放,允许患者重试 |
| | | String idemKey = null; |
| | | try { |
| | | //参数解密 |
| | | Long tid = null; |
| | |
| | | //根据 PID、TID、SUBID 构建需要处理的 cache key |
| | | String scriptCacheKeyPrefix = pid + "-" + tid + "-" + subid; |
| | | Set<String> targetKeys = new HashSet<>(); |
| | | |
| | | // ---------------- 幂等:同一份问卷 60 秒内只处理一次 ---------------- |
| | | // 本方法是 @Async 异步执行,患者端重复点击/网络重试会让它并发跑多次, |
| | | // 每次都会 upsert 一遍明细;并发时彼此看不到对方还没提交的行 -> 插入重复副本 |
| | | // (nhfy 2026-09-08 就是这样堆出 2~6 份,并且把 del_flag 一起写坏)。 |
| | | idemKey = "subtaskAnswer:submit:" + scriptCacheKeyPrefix + ":" |
| | | + (serviceSubTaskAnswerReq == null || serviceSubTaskAnswerReq.getType() == null ? "all" : serviceSubTaskAnswerReq.getType()); |
| | | if (!redisCache.setCacheObjectIfAbsent(idemKey, "1", 60, TimeUnit.SECONDS)) { |
| | | log.warn("[{}] 检测到重复提交,本次忽略:{}", threadName, idemKey); |
| | | return 0; |
| | | } |
| | | |
| | | if (serviceSubTaskAnswerReq != null && serviceSubTaskAnswerReq.getType() != null) { |
| | | if (serviceSubTaskAnswerReq.getType() == 2) { |
| | |
| | | return 1; |
| | | } catch (Exception e) { |
| | | log.error("[{}] 异步处理保存问卷答案发生异常 : {},请求的对象为:{}", Thread.currentThread().getName(), e.getMessage(), e, serviceSubTaskAnswerReq); |
| | | // 处理失败时释放幂等键,允许患者重新提交 |
| | | if (idemKey != null) { |
| | | try { |
| | | redisCache.deleteObject(idemKey); |
| | | } catch (Exception ignore) { |
| | | log.warn("[{}] 释放幂等键失败:{}", Thread.currentThread().getName(), idemKey); |
| | | } |
| | | } |
| | | throw new RuntimeException("保存问卷答案失败", e); |
| | | } |
| | | } |
| | |
| | | //住院 |
| | | PatMedInhosp patMedInhosp = new PatMedInhosp(); |
| | | patMedInhosp.setSerialnum(serviceSubTaskMYDAnswerReq.getSerialnum()); |
| | | patMedInhospList = patMedInhospMapper.selectPatMedInhospList(patMedInhosp); |
| | | patMedInhospList = inhospVisitFacade.selectPatMedInhospList(patMedInhosp); |
| | | if (CollectionUtils.isNotEmpty(patMedInhospList)) patid = patMedInhospList.get(0).getPatid(); |
| | | } else if (serviceSubTaskMYDAnswerReq.getMzzy().equals("2")) { |
| | | //门诊 |
| | | PatMedOuthospQueryReq patMedOuthosp = new PatMedOuthospQueryReq(); |
| | | patMedOuthosp.setSerialnum(serviceSubTaskMYDAnswerReq.getSerialnum()); |
| | | patMedOuthosps = patMedOuthospMapper.callSpQueryOuthosp(patMedOuthosp); |
| | | patMedOuthosps = outhospDataFacade.callSpQueryOuthosp(patMedOuthosp); |
| | | if (CollectionUtils.isNotEmpty(patMedOuthosps)) patid = patMedOuthosps.get(0).getPatid(); |
| | | } else if (serviceSubTaskMYDAnswerReq.getMzzy().equals("3")) { |
| | | //指定一个部门,用来处理投诉建议 |
| | |
| | | serviceSubtask.setLibtemplateid(Long.valueOf(serviceTask.getLibtemplateid())); |
| | | if (CollectionUtils.isNotEmpty(patMedInhospList)) { |
| | | serviceSubtask.setHospType("2"); |
| | | serviceSubtask.setDeptname(patMedInhospList.get(0).getDeptname()); |
| | | serviceSubtask.setDeptcode(patMedInhospList.get(0).getDeptcode()); |
| | | serviceSubtask.setLeaveldeptname(patMedInhospList.get(0).getDeptname()); |
| | | serviceSubtask.setLeaveldeptcode(patMedInhospList.get(0).getDeptcode()); |
| | | serviceSubtask.setLeavehospitaldistrictcode(patMedInhospList.get(0).getLeavehospitaldistrictcode()); |
| | | serviceSubtask.setLeavehospitaldistrictname(patMedInhospList.get(0).getLeavehospitaldistrictname()); |
| | | serviceSubtask.setDrname(patMedInhospList.get(0).getDrname()); |
| | |
| | | serviceSubtask.setNurseName(patMedInhospList.get(0).getNurseName()); |
| | | serviceSubtask.setNurseId(patMedInhospList.get(0).getNurseId()); |
| | | serviceSubtask.setInhospid(patMedInhospList.get(0).getInhospid()); |
| | | serviceSubtask.setCampusid(patMedInhospList.get(0).getCampusid()); |
| | | serviceSubtask.setServiceType("6"); |
| | | } else if (CollectionUtils.isNotEmpty(patMedOuthosps)) { |
| | | serviceSubtask.setHospType("1"); |
| | | serviceSubtask.setAdmindate(patMedOuthosps.get(0).getAdmitdate()); |
| | | serviceSubtask.setHospno(patMedOuthosps.get(0).getOuthospno()); |
| | | serviceSubtask.setDeptname(patMedOuthosps.get(0).getDeptname()); |
| | | serviceSubtask.setDeptcode(patMedOuthosps.get(0).getDeptcode()); |
| | | serviceSubtask.setLeaveldeptname(patMedOuthosps.get(0).getDeptname()); |
| | | serviceSubtask.setLeaveldeptcode(patMedOuthosps.get(0).getDeptcode()); |
| | | serviceSubtask.setDrname(patMedOuthosps.get(0).getDrname()); |
| | | serviceSubtask.setDrcode(patMedOuthosps.get(0).getDrcode()); |
| | | serviceSubtask.setCampusid(patMedOuthosps.get(0).getCampusid()); |
| | | serviceSubtask.setServiceType("14"); |
| | | } |
| | | if (!Objects.isNull(patArchive)) { |
| | |
| | | serviceSubtask.setSfzh(patArchive.getIdcardno()); |
| | | serviceSubtask.setAddr(patArchive.getPlaceOfResidence()); |
| | | serviceSubtask.setOrgid(patArchive.getOrgid()); |
| | | // 院区兜底:住院/门急诊记录的院区在本库常为空,用患者档案的院区补上, |
| | | // 否则统计接口(@AddOrgId 按登录用户院区过滤 service_subtask.campusid)会把这些子任务整批丢掉 |
| | | if (StringUtils.isEmpty(serviceSubtask.getCampusid())) { |
| | | serviceSubtask.setCampusid(patArchive.getCampusid()); |
| | | } |
| | | } |
| | | if (StringUtils.isNotEmpty(dealDept)) { |
| | | //投诉建议的处理部门 |
| | | serviceSubtask.setDeptcode(dealDept); |
| | | serviceSubtask.setLeaveldeptcode(dealDept); |
| | | serviceSubtask.setServiceType("15"); |
| | | } |
| | | serviceSubtask.setSubmit(1L); |
| | |
| | | |
| | | /** |
| | | * 批量查询已存在的明细记录ID,按scriptid分组(避免循环中逐条查询) |
| | | * <p> |
| | | * ⚠️ 2026-10-21:<b>不再过滤 del_flag</b>,同一 scriptid 取 id 最大(最新)的那一行。 |
| | | * 原来只认 del_flag=0,明细被逻辑删除后就查不到 → 下一次保存必然 INSERT 新副本, |
| | | * 同一患者同一题会堆出 2~6 条重复明细(nhfy 2026-09-08)。 |
| | | * 命中被删副本时由调用方再调 {@code reviveServiceSubtaskDetail} 置回 '0'。 |
| | | */ |
| | | private Map<Long, Long> getExistingDetailIdMap(Long subid) { |
| | | if (subid == null) return Collections.emptyMap(); |
| | |
| | | if (CollectionUtils.isEmpty(details)) return Collections.emptyMap(); |
| | | Map<Long, Long> map = new HashMap<>(); |
| | | for (ServiceSubtaskDetail d : details) { |
| | | if ((d.getDelFlag() == null || "0".equals(d.getDelFlag())) && d.getScriptid() != null && !map.containsKey(d.getScriptid())) { |
| | | if (d.getScriptid() == null || d.getId() == null) continue; |
| | | Long existId = map.get(d.getScriptid()); |
| | | if (existId == null || d.getId() > existId) { |
| | | map.put(d.getScriptid(), d.getId()); |
| | | } |
| | | } |
| | |
| | | serviceSubtaskDetail.setId(existDetailId); |
| | | serviceSubtaskDetail.setUpdateTime(new Date()); |
| | | serviceSubtaskDetailMapper.updateServiceSubtaskDetail(serviceSubtaskDetail); |
| | | //命中历史被逻辑删除的副本时置回 del_flag='0'(否则答案写进去了却查不到,且下次又会新增副本) |
| | | serviceSubtaskDetailMapper.reviveServiceSubtaskDetail(existDetailId); |
| | | } else { |
| | | serviceSubtaskDetailMapper.insertServiceSubtaskDetail(serviceSubtaskDetail); |
| | | } |
| | |
| | | serviceSubtaskDetail.setId(existServiceSubtaskDetail.getId()); |
| | | serviceSubtaskDetail.setUpdateTime(new Date()); |
| | | serviceSubtaskDetailMapper.updateServiceSubtaskDetail(serviceSubtaskDetail); |
| | | //命中历史被逻辑删除的副本时置回 del_flag='0',避免重复副本 |
| | | serviceSubtaskDetailMapper.reviveServiceSubtaskDetail(existServiceSubtaskDetail.getId()); |
| | | } else { |
| | | serviceSubtaskDetailMapper.insertServiceSubtaskDetail(serviceSubtaskDetail); |
| | | } |