ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/PatMedInhospController.java
@@ -29,6 +29,7 @@ import javax.servlet.http.HttpServletResponse; import java.util.List; import java.util.Map; /** * 患者住院记录Controller @@ -274,7 +275,11 @@ @ApiOperation("addManualVisit") @PostMapping("/addManualVisit") public AjaxResult addManualVisit(@RequestBody ManualVisitDTO manualVisitDTO) { return success(patMedInhospService.addManualVisit(manualVisitDTO)); Map<String, Object> result = patMedInhospService.addManualVisit(manualVisitDTO); if (Boolean.TRUE.equals(result.get("hasDuplicate"))) { return AjaxResult.warn((String) result.get("tipMessage"), result); } return success(result); } /** ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java
@@ -713,6 +713,16 @@ * taskSituation5Info - taskSituation = 5 * taskSituation6Info - taskSituation = 7 * manuallyCalledInfo - isManuallyCalled = 1L * manualCallTimeoutInfo - visitCount = 1 && sendstate = 7 && isManuallyCalled = 0 * manualCallTimeoutAgainInfo - visitCount > 1 && sendstate = 7 && isManuallyCalled = 0 * pendingFollowUpAllInfo - visitCount = 1 && sendstate in (1,2,3,7) * pendingFollowUpAllAgainInfo - visitCount > 1 && sendstate in (1,2,3,7) * pendingFollowUpWaitSendInfo - visitCount = 1 && sendstate in (1,2) * pendingFollowUpWaitSendAgainInfo - visitCount > 1 && sendstate in (1,2) * pendingFollowUpSentInfo - visitCount = 1 && sendstate in (3,7) * pendingFollowUpSentAgainInfo - visitCount > 1 && sendstate in (3,7) * sentInfo - visitCount = 1 && sendstate = 3 * sentAgainInfo - visitCount > 1 && sendstate = 3 * firstDischargeCountInfo - visitCount = 1 * againFollowUpPushCountInfo - visitCount = 2 && visitDeptCode = sys.smartor.center.dept * filterCountList - taskSituation = 6 && sendstate = 4 smartor/src/main/java/com/smartor/domain/ServiceSubtaskStatistic.java
@@ -274,6 +274,46 @@ @Excel(name = " 二次待随访 ") private Long pendingFollowUpAgain = 0L; @ApiModelProperty(value = "首次随访人工电话超时(sendstate=7 且 isManuallyCalled=0)") @Excel(name = " 首次随访人工电话超时 ") private Long manualCallTimeout = 0L; @ApiModelProperty(value = "再次随访人工电话超时(sendstate=7 且 isManuallyCalled=0)") @Excel(name = " 再次随访人工电话超时 ") private Long manualCallTimeoutAgain = 0L; @ApiModelProperty(value = "首次待随访全部(sendstate in 1,2,3,7)") @Excel(name = " 首次待随访全部 ") private Long pendingFollowUpAll = 0L; @ApiModelProperty(value = "再次待随访全部(sendstate in 1,2,3,7)") @Excel(name = " 再次待随访全部 ") private Long pendingFollowUpAllAgain = 0L; @ApiModelProperty(value = "首次待随访待发送(sendstate in 1,2)") @Excel(name = " 首次待随访待发送 ") private Long pendingFollowUpWaitSend = 0L; @ApiModelProperty(value = "再次待随访待发送(sendstate in 1,2)") @Excel(name = " 再次待随访待发送 ") private Long pendingFollowUpWaitSendAgain = 0L; @ApiModelProperty(value = "首次待随访已发送(sendstate in 3,7)") @Excel(name = " 首次待随访已发送 ") private Long pendingFollowUpSent = 0L; @ApiModelProperty(value = "再次待随访已发送(sendstate in 3,7)") @Excel(name = " 再次待随访已发送 ") private Long pendingFollowUpSentAgain = 0L; @ApiModelProperty(value = "首次已发送(sendstate=3)") @Excel(name = " 首次已发送 ") private Long sent = 0L; @ApiModelProperty(value = "再次已发送(sendstate=3)") @Excel(name = " 再次已发送 ") private Long sentAgain = 0L; @ApiModelProperty(value = "二次随访成功") @Excel(name = " 二次随访成功 ") private Long followUpSuccessAgain = 0L; smartor/src/main/java/com/smartor/mapper/PatArchiveMapper.java
@@ -24,6 +24,14 @@ */ public PatArchive selectPatArchiveByPatid(Long patid); /** * 锁定患者档案行,串行化同一患者的人工随访创建 * * @param patid 患者档案主键 * @return 患者档案主键,档案不存在或已删除时返回 null */ Long lockPatArchiveByPatid(@Param("patid") Long patid); List<PatArchive> selectPatArchiveByPatnos(@Param("patnos") List<String> patnos); List<PatArchive> selectPatArchiveByPatidHiss(@Param("patidHiss") List<String> patidHiss); smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java
@@ -168,6 +168,18 @@ @Param("sendstates") List<Long> sendstates); /** * 统计患者指定任务下未完成的随访任务数量(用于人工随访重复校验) * * @param patid 患者ID * @param taskid 任务ID * @param sendstates 未完成状态集合 * @return 未完成任务数量 */ int countUnfinishedServiceSubtaskByPatidAndTaskid(@Param("patid") Long patid, @Param("taskid") Long taskid, @Param("sendstates") List<Long> sendstates); /** * 删除单一任务(随访) * * @return 结果 smartor/src/main/java/com/smartor/service/impl/PatMedInhospServiceImpl.java
@@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; @@ -2009,10 +2010,11 @@ /** * 人工随访:删除患者出院记录对应的未完成随访任务,并按指定任务重新生成。 * 未完成状态口径:sendstate in (1,2,3,7)。 * 删除旧任务口径:sendstate in (1,2,3,7); * 重复任务校验口径:sendstate in (1,2,3,5,7),已有相同 taskid 未完成任务的患者跳过。 */ @Override @Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED) public Map<String, Object> addManualVisit(ManualVisitDTO manualVisitDTO) { if (manualVisitDTO == null) { throw new BaseException("入参不能为空"); @@ -2029,20 +2031,27 @@ throw new BaseException("人工随访任务不存在,taskid:" + manualVisitDTO.getTaskid()); } LinkedHashSet<Long> patids = manualVisitDTO.getPatids().stream() // 升序处理,配合患者档案行锁,避免批量请求交叉加锁产生死锁 List<Long> patids = manualVisitDTO.getPatids().stream() .filter(Objects::nonNull) .collect(Collectors.toCollection(LinkedHashSet::new)); .distinct() .sorted() .collect(Collectors.toList()); if (patids.isEmpty()) { throw new BaseException("患者patid集合不能为空"); } String config = iSysConfigService.selectConfigByKey("visit.early.day"); List<Long> unfinishedSendstates = Arrays.asList(1L, 2L, 3L, 7L); // 与前端待随访口径保持一致,发送失败(5)也视为已有未完成任务 List<Long> activeSendstates = Arrays.asList(1L, 2L, 3L, 5L, 7L); int deletedCount = 0; int generatedCount = 0; List<Long> noDischargeRecordPatids = new ArrayList<>(); List<Long> noArchivePatids = new ArrayList<>(); List<Long> generateFailedPatids = new ArrayList<>(); List<Long> duplicatePatids = new ArrayList<>(); List<String> duplicatePatientNames = new ArrayList<>(); for (Long patid : patids) { // 查询患者全部出院记录,SQL 按出院时间倒序,第一条作为本次生成任务的出院记录 @@ -2067,6 +2076,26 @@ PatArchive patArchive = patArchiveMapper.selectPatArchiveByPatid(patid); if (Objects.isNull(patArchive)) { noArchivePatids.add(patid); continue; } // 锁定患者档案行,串行化同一患者的并发创建请求 Long lockedPatid = patArchiveMapper.lockPatArchiveByPatid(patid); if (Objects.isNull(lockedPatid)) { noArchivePatids.add(patid); continue; } // 同一患者在同一 taskid 下已有未完成随访任务时,跳过该患者,避免重复创建 int existingSubtaskCount = serviceSubtaskMapper.countUnfinishedServiceSubtaskByPatidAndTaskid( patid, serviceTask.getTaskid(), activeSendstates); if (existingSubtaskCount > 0) { duplicatePatids.add(patid); String patientName = StringUtils.isNotEmpty(patArchive.getName()) ? patArchive.getName() : String.valueOf(patid); duplicatePatientNames.add(patientName + "(" + patid + ")"); log.info("addManualVisit 患者已有相同服务,跳过生成,patid:{},taskid:{},已有任务数:{}", patid, serviceTask.getTaskid(), existingSubtaskCount); continue; } @@ -2111,14 +2140,29 @@ patid, serviceTask.getTaskid(), serviceSubtask.getId()); } String tipMessage = null; if (CollectionUtils.isNotEmpty(duplicatePatids)) { String taskName = StringUtils.isNotEmpty(serviceTask.getTaskName()) ? serviceTask.getTaskName() : String.valueOf(serviceTask.getTaskid()); tipMessage = "患者" + String.join("、", duplicatePatientNames) + "已有相同服务【" + taskName + "】,已跳过"; if (generatedCount > 0) { tipMessage += ";其余患者已处理"; } } Map<String, Object> result = new HashMap<>(); result.put("deletedCount", deletedCount); result.put("generatedCount", generatedCount); result.put("noDischargeRecordPatids", noDischargeRecordPatids); result.put("noArchivePatids", noArchivePatids); result.put("generateFailedPatids", generateFailedPatids); log.info("addManualVisit 处理完成,patid数:{},taskid:{},删除未完成任务数:{},生成任务数:{}", patids.size(), serviceTask.getTaskid(), deletedCount, generatedCount); result.put("duplicatePatids", duplicatePatids); result.put("duplicatePatientNames", duplicatePatientNames); result.put("hasDuplicate", CollectionUtils.isNotEmpty(duplicatePatids)); result.put("tipMessage", tipMessage); log.info("addManualVisit 处理完成,patid数:{},taskid:{},删除未完成任务数:{},生成任务数:{},重复跳过数:{}", patids.size(), serviceTask.getTaskid(), deletedCount, generatedCount, duplicatePatids.size()); return result; } smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
@@ -3801,6 +3801,7 @@ //首次出院随访 if (serviceSubtask.getVisitCount() != null && serviceSubtask.getVisitCount() == 1) { applyFirstFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask); //首次应随访 if (serviceSubtask.getSendstate() != null && !serviceSubtask.getSendstate().equals(4L)) { serviceSubtaskStatistic.setNeedFollowUp(serviceSubtaskStatistic.getNeedFollowUp() + 1L); @@ -3922,6 +3923,7 @@ } }//二次出院随访 else if (serviceSubtask.getVisitCount() != null && serviceSubtask.getVisitCount() > 1) { applyAgainFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask); //再次应随访 if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) { serviceSubtaskStatistic.setNeedFollowUpAgain(serviceSubtaskStatistic.getNeedFollowUpAgain() + 1L); @@ -4073,6 +4075,60 @@ } /** * 市一统计口径:首次待随访、已发送及人工电话超时。 */ private void applyFirstFollowUpExtraStatistics(ServiceSubtaskStatistic statistic, ServiceSubtask serviceSubtask) { if (serviceSubtask.getVisitCount() == null || serviceSubtask.getVisitCount() != 1 || serviceSubtask.getSendstate() == null) { return; } Long sendstate = serviceSubtask.getSendstate(); if (sendstate.equals(3L)) { statistic.setSent(statistic.getSent() + 1L); } if (sendstate.equals(7L) && isManuallyNotCalled(serviceSubtask)) { statistic.setManualCallTimeout(statistic.getManualCallTimeout() + 1L); } if (sendstate.equals(1L) || sendstate.equals(2L) || sendstate.equals(3L) || sendstate.equals(7L)) { statistic.setPendingFollowUpAll(statistic.getPendingFollowUpAll() + 1L); } if (sendstate.equals(1L) || sendstate.equals(2L)) { statistic.setPendingFollowUpWaitSend(statistic.getPendingFollowUpWaitSend() + 1L); } if (sendstate.equals(3L) || sendstate.equals(7L)) { statistic.setPendingFollowUpSent(statistic.getPendingFollowUpSent() + 1L); } } /** * 市一统计口径:再次待随访、已发送及人工电话超时。 */ private void applyAgainFollowUpExtraStatistics(ServiceSubtaskStatistic statistic, ServiceSubtask serviceSubtask) { if (serviceSubtask.getVisitCount() == null || serviceSubtask.getVisitCount() <= 1 || serviceSubtask.getSendstate() == null) { return; } Long sendstate = serviceSubtask.getSendstate(); if (sendstate.equals(3L)) { statistic.setSentAgain(statistic.getSentAgain() + 1L); } if (sendstate.equals(7L) && isManuallyNotCalled(serviceSubtask)) { statistic.setManualCallTimeoutAgain(statistic.getManualCallTimeoutAgain() + 1L); } if (sendstate.equals(1L) || sendstate.equals(2L) || sendstate.equals(3L) || sendstate.equals(7L)) { statistic.setPendingFollowUpAllAgain(statistic.getPendingFollowUpAllAgain() + 1L); } if (sendstate.equals(1L) || sendstate.equals(2L)) { statistic.setPendingFollowUpWaitSendAgain(statistic.getPendingFollowUpWaitSendAgain() + 1L); } if (sendstate.equals(3L) || sendstate.equals(7L)) { statistic.setPendingFollowUpSentAgain(statistic.getPendingFollowUpSentAgain() + 1L); } } private boolean isManuallyNotCalled(ServiceSubtask serviceSubtask) { return serviceSubtask.getIsManuallyCalled() != null && serviceSubtask.getIsManuallyCalled().equals(0L); } /** * 首次随访统计 * * @param serviceSubtaskList @@ -4093,6 +4149,7 @@ serviceSubtaskStatistic.setDischargeCount(serviceSubtaskList.size()); for (ServiceSubtask serviceSubtask : serviceSubtaskList) { applyFirstFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask); //无需随访人次 if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 4) { serviceSubtaskStatistic.setNonFollowUp(serviceSubtaskStatistic.getNonFollowUp() + 1L); @@ -4266,6 +4323,7 @@ //出院人次 serviceSubtaskStatistic.setDischargeCount(serviceSubtaskList.size()); for (ServiceSubtask serviceSubtask : serviceSubtaskList) { applyAgainFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask); //无需随访人次 if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 4) { serviceSubtaskStatistic.setNonFollowUp(serviceSubtaskStatistic.getNonFollowUp() + 1L); @@ -4844,6 +4902,16 @@ * taskSituation5Info - taskSituation = 5 * taskSituation6Info - taskSituation = 7 * manuallyCalledInfo - isManuallyCalled = 1L * manualCallTimeoutInfo - visitCount = 1 && sendstate = 7 && isManuallyCalled = 0 * manualCallTimeoutAgainInfo - visitCount > 1 && sendstate = 7 && isManuallyCalled = 0 * pendingFollowUpAllInfo - visitCount = 1 && sendstate in (1,2,3,7) * pendingFollowUpAllAgainInfo - visitCount > 1 && sendstate in (1,2,3,7) * pendingFollowUpWaitSendInfo - visitCount = 1 && sendstate in (1,2) * pendingFollowUpWaitSendAgainInfo - visitCount > 1 && sendstate in (1,2) * pendingFollowUpSentInfo - visitCount = 1 && sendstate in (3,7) * pendingFollowUpSentAgainInfo - visitCount > 1 && sendstate in (3,7) * sentInfo - visitCount = 1 && sendstate = 3 * sentAgainInfo - visitCount > 1 && sendstate = 3 * filterCountList - taskSituation = 6 && sendstate = 4 * */ @@ -5051,6 +5119,48 @@ break; case "manuallyCalledInfo": serviceSubtaskCountReq.setIsManuallyCalled(1L); break; case "manualCallTimeoutInfo": serviceSubtaskCountReq.setVisitCount(1); serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(7L))); serviceSubtaskCountReq.setIsManuallyCalled(0L); break; case "manualCallTimeoutAgainInfo": serviceSubtaskCountReq.setVisitCount(2); serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(7L))); serviceSubtaskCountReq.setIsManuallyCalled(0L); break; case "pendingFollowUpAllInfo": serviceSubtaskCountReq.setVisitCount(1); serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 7L))); break; case "pendingFollowUpAllAgainInfo": serviceSubtaskCountReq.setVisitCount(2); serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 7L))); break; case "pendingFollowUpWaitSendInfo": serviceSubtaskCountReq.setVisitCount(1); serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L))); break; case "pendingFollowUpWaitSendAgainInfo": serviceSubtaskCountReq.setVisitCount(2); serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L))); break; case "pendingFollowUpSentInfo": serviceSubtaskCountReq.setVisitCount(1); serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(3L, 7L))); break; case "pendingFollowUpSentAgainInfo": serviceSubtaskCountReq.setVisitCount(2); serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(3L, 7L))); break; case "sentInfo": serviceSubtaskCountReq.setVisitCount(1); serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(3L))); break; case "sentAgainInfo": serviceSubtaskCountReq.setVisitCount(2); serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(3L))); break; default: break; @@ -5413,6 +5523,16 @@ total.setAbnormal(0L); total.setNeedFollowUpAgain(0L); total.setPendingFollowUpAgain(0L); total.setManualCallTimeout(0L); total.setManualCallTimeoutAgain(0L); total.setPendingFollowUpAll(0L); total.setPendingFollowUpAllAgain(0L); total.setPendingFollowUpWaitSend(0L); total.setPendingFollowUpWaitSendAgain(0L); total.setPendingFollowUpSent(0L); total.setPendingFollowUpSentAgain(0L); total.setSent(0L); total.setSentAgain(0L); total.setFollowUpSuccessAgain(0L); total.setFollowUpFailAgain(0L); total.setManualAgain(0L); @@ -5447,6 +5567,16 @@ total.setAbnormal(total.getAbnormal() + (stat.getAbnormal() != null ? stat.getAbnormal() : 0L)); total.setNeedFollowUpAgain(total.getNeedFollowUpAgain() + (stat.getNeedFollowUpAgain() != null ? stat.getNeedFollowUpAgain() : 0L)); total.setPendingFollowUpAgain(total.getPendingFollowUpAgain() + (stat.getPendingFollowUpAgain() != null ? stat.getPendingFollowUpAgain() : 0L)); total.setManualCallTimeout(total.getManualCallTimeout() + (stat.getManualCallTimeout() != null ? stat.getManualCallTimeout() : 0L)); total.setManualCallTimeoutAgain(total.getManualCallTimeoutAgain() + (stat.getManualCallTimeoutAgain() != null ? stat.getManualCallTimeoutAgain() : 0L)); total.setPendingFollowUpAll(total.getPendingFollowUpAll() + (stat.getPendingFollowUpAll() != null ? stat.getPendingFollowUpAll() : 0L)); total.setPendingFollowUpAllAgain(total.getPendingFollowUpAllAgain() + (stat.getPendingFollowUpAllAgain() != null ? stat.getPendingFollowUpAllAgain() : 0L)); total.setPendingFollowUpWaitSend(total.getPendingFollowUpWaitSend() + (stat.getPendingFollowUpWaitSend() != null ? stat.getPendingFollowUpWaitSend() : 0L)); total.setPendingFollowUpWaitSendAgain(total.getPendingFollowUpWaitSendAgain() + (stat.getPendingFollowUpWaitSendAgain() != null ? stat.getPendingFollowUpWaitSendAgain() : 0L)); total.setPendingFollowUpSent(total.getPendingFollowUpSent() + (stat.getPendingFollowUpSent() != null ? stat.getPendingFollowUpSent() : 0L)); total.setPendingFollowUpSentAgain(total.getPendingFollowUpSentAgain() + (stat.getPendingFollowUpSentAgain() != null ? stat.getPendingFollowUpSentAgain() : 0L)); total.setSent(total.getSent() + (stat.getSent() != null ? stat.getSent() : 0L)); total.setSentAgain(total.getSentAgain() + (stat.getSentAgain() != null ? stat.getSentAgain() : 0L)); total.setFollowUpSuccessAgain(total.getFollowUpSuccessAgain() + (stat.getFollowUpSuccessAgain() != null ? stat.getFollowUpSuccessAgain() : 0L)); total.setFollowUpFailAgain(total.getFollowUpFailAgain() + (stat.getFollowUpFailAgain() != null ? stat.getFollowUpFailAgain() : 0L)); total.setManualAgain(total.getManualAgain() + (stat.getManualAgain() != null ? stat.getManualAgain() : 0L)); smartor/src/main/resources/mapper/smartor/PatArchiveMapper.xml
@@ -286,6 +286,14 @@ where id = #{id} and del_flag=0 </select> <!-- 串行化同一患者的人工随访创建,避免并发重复校验失效 --> <select id="lockPatArchiveByPatid" resultType="java.lang.Long"> select id from pat_archive where id = #{patid} and del_flag = 0 for update </select> <select id="selectPatArchiveByPatnos" resultMap="PatArchiveResult"> SELECT id, patientno FROM pat_archive smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml
@@ -1671,6 +1671,19 @@ </foreach> </update> <!-- 人工随访重复校验:同一患者同一任务下是否已有未完成随访任务 --> <select id="countUnfinishedServiceSubtaskByPatidAndTaskid" resultType="int"> select count(1) from service_subtask where del_flag = 0 and patid = #{patid} and taskid = #{taskid} and sendstate in <foreach collection="sendstates" item="sendstate" open="(" separator="," close=")"> #{sendstate} </foreach> </select> <select id="getSfStatistics" parameterType="com.smartor.domain.ServiceSubtaskCountReq" resultMap="ServiceSubtaskResult"> select id,