| | |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.util.*; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.regex.Pattern; |
| | | import java.util.stream.Collectors; |
| | | |
| | |
| | | import com.ruoyi.task.mapper.ServiceOutPathMapper; |
| | | import com.ruoyi.task.service.IServiceOutPathService; |
| | | import com.ruoyi.task.domain.ServiceSubtaskPreachform; |
| | | import com.ruoyi.task.api.spi.TaskPreachformPort; |
| | | import com.ruoyi.task.mapper.ServiceSubtaskPreachformMapper; |
| | | import com.ruoyi.task.api.spi.TaskArchivePort; |
| | | import com.ruoyi.task.api.spi.TaskFtpPort; |
| | | import com.ruoyi.task.api.spi.TaskSysConfigPort; |
| | | import com.ruoyi.task.api.spi.TaskSysDeptPort; |
| | | import com.ruoyi.task.common.ScheduleSubtaskBuilder; |
| | | import com.ruoyi.task.domain.ServiceTaskScheduleVO; |
| | | import com.ruoyi.task.service.IServiceSubtaskService; |
| | |
| | | import com.ruoyi.task.domain.ServiceSubtaskTemplateVO; |
| | | import com.ruoyi.task.domain.ServiceTaskVO; |
| | | import com.ruoyi.task.domain.VO.TransferDeptVO; |
| | | import com.ruoyi.system.service.ISysConfigService; |
| | | import com.ruoyi.system.service.ISysDeptService; |
| | | import com.ruoyi.system.mapper.ext.SysDept2Mapper; |
| | | import com.ruoyi.task.api.contract.OperationSubtaskWriter; |
| | | |
| | | /** |
| | | * 单一任务(随访)Service业务层处理 |
| | |
| | | @Slf4j |
| | | @Service |
| | | public class ServiceSubtaskServiceImpl implements IServiceSubtaskService { |
| | | |
| | | /** |
| | | * 随访子任务组装器(原 smartor 侧 IPatMedInhospService#boxedServiceSubtask 在本模块的落地实现)。 |
| | | * <p> |
| | | * 二次随访要用「下一任务配置 + 出院记录 + 患者档案」组装新的 service_subtask, |
| | | * 这段口径与长期任务生成完全一致,因此直接复用本模块已有的组装器,不再复制一份。 |
| | | */ |
| | | @Autowired |
| | | private OperationSubtaskWriter operationSubtaskWriter; |
| | | |
| | | /** |
| | | * 满意度模块对外门面(常驻 Bean)。 |
| | |
| | | private ServiceSubtaskMapper serviceSubtaskMapper; |
| | | |
| | | @Autowired |
| | | private TaskPreachformPort taskPreachformPort; |
| | | private ServiceSubtaskPreachformMapper taskPreachformPort; |
| | | |
| | | @Autowired |
| | | private ServiceSubtaskDetailMapper serviceSubtaskDetailMapper; |
| | |
| | | private TaskArchivePort taskArchivePort; |
| | | |
| | | @Autowired |
| | | private ScheduleSubtaskBuilder scheduleSubtaskBuilder; |
| | | private ISysDeptService sysDeptService; |
| | | |
| | | @Autowired |
| | | private TaskFtpPort taskFtpPort; |
| | | private ScheduleSubtaskBuilder scheduleSubtaskBuilder; |
| | | |
| | | |
| | | |
| | |
| | | |
| | | |
| | | @Autowired |
| | | private TaskSysConfigPort taskSysConfigPort; |
| | | private ISysConfigService sysConfigService; |
| | | |
| | | /** |
| | | * 消息发送方工厂(短信/微信公众号),替代原来直接调用各站点发送代码的写法 |
| | |
| | | |
| | | |
| | | @Autowired |
| | | private TaskSysDeptPort taskSysDeptPort; |
| | | private SysDept2Mapper sysDept2Mapper; |
| | | |
| | | @Value("${spring.profiles.active}") |
| | | private String active; |
| | |
| | | private ServiceOutPathMapper serviceOutPathMapper; |
| | | |
| | | private static final String KEY_SEP = "\u0001"; |
| | | |
| | | /** 二次随访生成锁前缀,防止同一原任务并发生成多条二次随访 */ |
| | | private static final String AGAIN_LOCK_KEY_PREFIX = "smartor:again:lock:"; |
| | | private static final long AGAIN_LOCK_SECONDS = 30L; |
| | | |
| | | /** |
| | | * 查询单一任务(随访) |
| | |
| | | patTaskRelevance.setDiagname(serviceSubtask1.getLeavediagname()); |
| | | patTaskRelevance.setPatid(serviceSubtask1.getPatid()); |
| | | patTaskRelevance.setSendStatus(serviceSubtask1.getSendstate()); |
| | | patTaskRelevance.setDeptcode(serviceSubtask1.getDeptcode()); |
| | | patTaskRelevance.setDeptName(serviceSubtask1.getDeptname()); |
| | | patTaskRelevance.setDeptcode(serviceSubtask1.getLeaveldeptcode()); |
| | | patTaskRelevance.setDeptName(serviceSubtask1.getLeaveldeptname()); |
| | | patTaskRelevance.setLeavehospitaldistrictcode(serviceSubtask1.getLeavehospitaldistrictcode()); |
| | | patTaskRelevance.setLeavehospitaldistrictname(serviceSubtask1.getLeavehospitaldistrictname()); |
| | | patTaskRelevance.setInhosptime(serviceSubtask1.getInhosptime()); |
| | |
| | | patTaskRelevance.setTelcode(serviceSubtask1.getPhone()); |
| | | patTaskRelevance.setFinishtime(serviceSubtask1.getFinishtime()); |
| | | patTaskRelevance.setAddr(serviceSubtask1.getAddr()); |
| | | patTaskRelevance.setDeptName(serviceSubtask1.getDeptname()); |
| | | patTaskRelevance.setDeptcode(serviceSubtask1.getDeptcode()); |
| | | patTaskRelevance.setDeptName(serviceSubtask1.getLeaveldeptname()); |
| | | patTaskRelevance.setDeptcode(serviceSubtask1.getLeaveldeptcode()); |
| | | patTaskRelevance.setBedNo(serviceSubtask1.getBedNo()); |
| | | patTaskRelevance.setDiagname(serviceSubtask1.getLeavediagname()); |
| | | patTaskRelevance.setPatid(serviceSubtask1.getPatid()); |
| | |
| | | List<ServiceSubtask> selectServiceSubtaskList = getServiceSubtasks(serviceSubtaskEntity); |
| | | |
| | | List<ServiceSubtaskRes> serviceSubtaskResList = new ArrayList<>(); |
| | | // 同一次请求(列表分页 / 全量导出)里,inhospid 与 patid 高度重复 |
| | | // (再次随访、同一患者多任务都会指向同一条住院记录/同一份档案)。 |
| | | // 原来每行都要 2 次单条查询,导出几千行就是几千×2 次往返;这里按 id 做进程内缓存, |
| | | // 把「每行 2 次」压成「每个不同 id 1 次」,取数语义不变。 |
| | | Map<Long, PatMedInhosp> inhospCache = new HashMap<>(); |
| | | Map<Long, PatArchive> archiveCache = new HashMap<>(); |
| | | for (ServiceSubtask serviceSubtask : selectServiceSubtaskList) { |
| | | ServiceSubtaskRes serviceSubtaskRes = DtoConversionUtils.sourceToTarget(serviceSubtask, ServiceSubtaskRes.class); |
| | | if (ObjectUtils.isNotEmpty(serviceSubtask.getContinueContent())) { |
| | |
| | | if (StringUtils.isEmpty(serviceSubtask.getCurrentPreachform())) { |
| | | if (StringUtils.isNotEmpty(serviceSubtask.getPreachform())) { |
| | | //默认取第一个 |
| | | serviceSubtaskRes.setCurrentPreachform(serviceSubtask.getPreachform().split(",")[0]); |
| | | serviceSubtaskRes.setCurrentPreachform(serviceSubtask.getPreachform().split(",")[0].trim()); |
| | | } |
| | | } |
| | | PatMedInhosp inhosp = inhospVisitFacade.selectPatMedInhospByInhospid(serviceSubtask.getInhospid()); |
| | | PatMedInhosp inhosp = loadInhospCached(inhospCache, serviceSubtask.getInhospid()); |
| | | if (ObjectUtils.isNotEmpty(inhosp)) { |
| | | PatArchive patArchive = taskArchivePort.findByPatid(inhosp.getPatid()); |
| | | PatArchive patArchive = loadArchiveCached(archiveCache, inhosp.getPatid()); |
| | | if (ObjectUtils.isNotEmpty(patArchive)) { |
| | | Map<String, String> stringStringMap = calculateAge(patArchive.getBirthdate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), LocalDate.now()); |
| | | serviceSubtaskRes.setAge(StringUtils.isNotEmpty(stringStringMap.get("age")) ? Long.valueOf(stringStringMap.get("age")) : null); |
| | |
| | | return serviceSubtaskResList; |
| | | } |
| | | |
| | | /** |
| | | * 按 inhospid 取住院记录(带进程内缓存)。 |
| | | * <p> |
| | | * 只是把「同一次请求内同一个 id 只查一次」这件事收口,<b>不改变取数语义</b>: |
| | | * 查不到(null)也会缓存,避免同一批数据里对不存在的 id 反复打库。 |
| | | * 缓存生命周期 = 单次 {@code patItem} 调用,不跨请求、不共享。 |
| | | */ |
| | | private PatMedInhosp loadInhospCached(Map<Long, PatMedInhosp> cache, Long inhospid) { |
| | | if (inhospid == null) { |
| | | return null; |
| | | } |
| | | if (!cache.containsKey(inhospid)) { |
| | | cache.put(inhospid, inhospVisitFacade.selectPatMedInhospByInhospid(inhospid)); |
| | | } |
| | | return cache.get(inhospid); |
| | | } |
| | | |
| | | /** |
| | | * 按 patid 取患者档案(带进程内缓存),语义同 {@link #loadInhospCached}。 |
| | | */ |
| | | private PatArchive loadArchiveCached(Map<Long, PatArchive> cache, Long patid) { |
| | | if (patid == null) { |
| | | return null; |
| | | } |
| | | if (!cache.containsKey(patid)) { |
| | | cache.put(patid, taskArchivePort.findByPatid(patid)); |
| | | } |
| | | return cache.get(patid); |
| | | } |
| | | |
| | | public List<ServiceSubtask> getServiceSubtasks(ServiceSubtaskEntity serviceSubtaskEntity) { |
| | | if (serviceSubtaskEntity.getSendstateView() != null) { |
| | | // 1:待随访(1 被领取、2 待发送、3 已发送、5 发送失败、7、超时);2:已完成( 6 已完成)、3:无需随访(4 不执行) |
| | | // 1:待随访(1 被领取、2 待发送、3 已发送、7 超时;发送失败(5) 单独统计);2:已完成( 6 已完成)、3:无需随访(4 不执行) |
| | | if (serviceSubtaskEntity.getSendstateView() == 1) |
| | | serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 5L, 7L))); |
| | | serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 7L))); |
| | | if (serviceSubtaskEntity.getSendstateView() == 2) |
| | | serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(6L))); |
| | | if (serviceSubtaskEntity.getSendstateView() == 3) |
| | |
| | | } |
| | | } |
| | | } |
| | | List<ServiceSubtask> selectServiceSubtaskList = this.selectServiceSubtaskList(serviceSubtaskEntity); |
| | | return selectServiceSubtaskList; |
| | | // 分页请求走「两阶段」:先只取当页 id,再按 id 回表。 |
| | | // 列表 select 约 100 列,深翻页时被 offset 跳过的每一行原本都要回表读整行; |
| | | // 只取 id 的查询能走覆盖索引并提前截断,代价小得多。 |
| | | if (serviceSubtaskEntity.getPageNum() != null && serviceSubtaskEntity.getPageSize() != null) { |
| | | return selectServiceSubtaskPage(serviceSubtaskEntity); |
| | | } |
| | | return this.selectServiceSubtaskList(serviceSubtaskEntity); |
| | | } |
| | | |
| | | /** |
| | | * 两阶段分页:① 只查 id(覆盖索引 + limit offset)② 按 id 回表取整行。 |
| | | * <p> |
| | | * 第二阶段复用 {@code selectServiceSubtaskList} 的 subIdList 条件回表, |
| | | * 必须把 pageNum/pageSize 摘掉 —— 否则会在「当页 id 集合」上再翻一次页。 |
| | | * <p> |
| | | * 统计缓存(Controller 的 {@code getPatItemCountWithCache})用 ReflectUtils 比对整个 |
| | | * entity 作为缓存命中条件,{@code subIdList} 参与比对,所以用完必须原样还原, |
| | | * 否则缓存永远不命中,每次请求都要重算一次聚合。 |
| | | */ |
| | | private List<ServiceSubtask> selectServiceSubtaskPage(ServiceSubtaskEntity serviceSubtaskEntity) { |
| | | List<Long> pageIds = serviceSubtaskMapper.selectServiceSubtaskIdsPage(serviceSubtaskEntity); |
| | | if (CollectionUtils.isEmpty(pageIds)) { |
| | | return new ArrayList<>(); |
| | | } |
| | | Integer pageNum = serviceSubtaskEntity.getPageNum(); |
| | | Integer pageSize = serviceSubtaskEntity.getPageSize(); |
| | | List<Long> originSubIdList = serviceSubtaskEntity.getSubIdList(); |
| | | serviceSubtaskEntity.setPageNum(null); |
| | | serviceSubtaskEntity.setPageSize(null); |
| | | serviceSubtaskEntity.setSubIdList(pageIds); |
| | | List<ServiceSubtask> rows; |
| | | try { |
| | | rows = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskEntity); |
| | | } finally { |
| | | serviceSubtaskEntity.setPageNum(pageNum); |
| | | serviceSubtaskEntity.setPageSize(pageSize); |
| | | serviceSubtaskEntity.setSubIdList(originSubIdList); |
| | | } |
| | | if (CollectionUtils.isEmpty(rows)) { |
| | | return new ArrayList<>(); |
| | | } |
| | | // 以第一阶段的 id 顺序为准重排:第二阶段的 order by 只保证「同一页内」有序, |
| | | // 遇到排序字段值相同的行时相对顺序不一定与第一阶段一致。 |
| | | Map<Long, ServiceSubtask> byId = new HashMap<>(Math.max(16, rows.size() * 2)); |
| | | for (ServiceSubtask row : rows) { |
| | | if (row.getId() != null) { |
| | | byId.put(row.getId(), row); |
| | | } |
| | | } |
| | | List<ServiceSubtask> ordered = new ArrayList<>(pageIds.size()); |
| | | for (Long id : pageIds) { |
| | | ServiceSubtask row = byId.get(id); |
| | | if (row != null) { |
| | | ordered.add(row); |
| | | } |
| | | } |
| | | return ordered; |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | @Override |
| | | public List<ServiceSubtask> patItem2(ServiceSubtaskEntity serviceSubtaskEntity) { |
| | | if (serviceSubtaskEntity.getSendstateView() != null) { |
| | | // 1:待随访(1 被领取、2 待发送、3 已发送、5 发送失败、7、超时);2:已完成( 6 已完成)、3:无需随访(4 不执行) |
| | | if (serviceSubtaskEntity.getSendstateView() == 1) |
| | | serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 5L, 7L))); |
| | | if (serviceSubtaskEntity.getSendstateView() == 2) |
| | | serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(6L))); |
| | | if (serviceSubtaskEntity.getSendstateView() == 3) |
| | | serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(4L))); |
| | | } |
| | | List<ServiceSubtask> serviceSubtaskList = new ArrayList<>(); |
| | | for (Long sendstate : serviceSubtaskEntity.getSendstates()) { |
| | | serviceSubtaskEntity.setSendstate(sendstate); |
| | | List<ServiceSubtask> serviceSubtasks = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskEntity); |
| | | serviceSubtaskList.addAll(serviceSubtasks); |
| | | if(CollectionUtils.isNotEmpty(serviceSubtaskEntity.getSendstates())){ |
| | | for (Long sendstate : serviceSubtaskEntity.getSendstates()) { |
| | | serviceSubtaskEntity.setSendstate(sendstate); |
| | | List<ServiceSubtask> serviceSubtasks = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskEntity); |
| | | serviceSubtaskList.addAll(serviceSubtasks); |
| | | } |
| | | } |
| | | return serviceSubtaskList; |
| | | } |
| | |
| | | result.putIfAbsent("xsf", 0); |
| | | result.putIfAbsent("dsf", 0); |
| | | result.putIfAbsent("ywc", 0); |
| | | result.putIfAbsent("cs", 0); |
| | | result.putIfAbsent("yc", 0); |
| | | result.putIfAbsent("jg", 0); |
| | | } |
| | |
| | | result.putIfAbsent("dsf", 0); |
| | | result.putIfAbsent("dfs", 0); |
| | | result.putIfAbsent("yfs", 0); |
| | | result.putIfAbsent("cs", 0); |
| | | result.putIfAbsent("ywc", 0); |
| | | result.putIfAbsent("yc", 0); |
| | | result.putIfAbsent("jg", 0); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 统一「随访病区」口径:一律以【出院记录】{@code pat_med_inhosp} 为准(2026-10-29 业务确认)。 |
| | | * |
| | | * <h3>为什么需要它</h3> |
| | | * 出院随访子任务的科室/病区本应来自出院记录,但「再次随访 / 手工新增」这两个入口是 |
| | | * <b>直接按前端传来的字段落库</b>的:前端不带这些字段时 |
| | | * {@code leaveldeptcode} / {@code leavehospitaldistrictcode} / {@code campusid} 就是空。 |
| | | * 而随访列表是按科室/病区过滤的({@code and leavehospitaldistrictcode = #{...}}), |
| | | * 空值不匹配任何值 → 这些任务"生成了但页面上查不到"(线上 2026-10 实测 314 条)。 |
| | | * |
| | | * <p>口径:<b>病区只认出院记录</b> —— 不按患者档案反推、也不用前端传的值。 |
| | | * 只对出院随访({@code service_type='2'})且能定位到出院记录({@code inhospid} 非空)的行生效; |
| | | * 其余情况原样放行,对满意度(6)/宣教/门诊等其它类型没有任何影响。 |
| | | * |
| | | * @param serviceSubtask 待落库的子任务(就地修改) |
| | | */ |
| | | private void alignDischargeWardFromInhosp(ServiceSubtask serviceSubtask) { |
| | | if (serviceSubtask == null || serviceSubtask.getInhospid() == null) { |
| | | return; |
| | | } |
| | | if (!"2".equals(serviceSubtask.getServiceType())) { |
| | | return; |
| | | } |
| | | PatMedInhosp inhosp; |
| | | try { |
| | | inhosp = inhospVisitFacade.selectPatMedInhospByInhospid(serviceSubtask.getInhospid()); |
| | | } catch (Exception e) { |
| | | log.warn("【随访任务】按 inhospid={} 取出院记录失败,病区沿用入参:{}", |
| | | serviceSubtask.getInhospid(), e.toString()); |
| | | return; |
| | | } |
| | | if (inhosp == null) { |
| | | log.warn("【随访任务】inhospid={} 查不到出院记录,病区沿用入参", serviceSubtask.getInhospid()); |
| | | return; |
| | | } |
| | | serviceSubtask.setLeaveldeptcode(inhosp.getLeaveldeptcode()); |
| | | serviceSubtask.setLeaveldeptname(inhosp.getLeaveldeptname()); |
| | | serviceSubtask.setLeavehospitaldistrictcode(inhosp.getLeavehospitaldistrictcode()); |
| | | serviceSubtask.setLeavehospitaldistrictname(inhosp.getLeavehospitaldistrictname()); |
| | | if (StringUtils.isNotEmpty(inhosp.getCampusid())) { |
| | | serviceSubtask.setCampusid(inhosp.getCampusid()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | serviceOutPath.setSubId(serviceSubtask1.getId()); |
| | | List<ServiceOutPath> serviceOutPaths = serviceOutPathMapper.selectServiceOutPathList(serviceOutPath); |
| | | if (CollectionUtils.isNotEmpty(serviceOutPaths)) |
| | | format = String.format("%03X", serviceOutPaths.get(0).getId()); |
| | | format = ShortCode.encode(serviceOutPaths.get(0).getId()); |
| | | else format = getOutPath(serviceSubtask1, rsaPublicKeyExample); |
| | | serviceSubtask = serviceSubtask1; |
| | | } else { |
| | |
| | | } |
| | | |
| | | serviceSubtask.setCreateTime(DateUtils.getNowDate()); |
| | | // 口径(2026-10-29 业务确认):出院随访的科室/病区一律以出院记录为准 |
| | | alignDischargeWardFromInhosp(serviceSubtask); |
| | | serviceSubtaskMapper.insertServiceSubtask(serviceSubtask); |
| | | //修改住院满意度状态(避免重复查询) |
| | | if (serviceSubtask.getServiceType() != null && "6".equals(serviceSubtask.getServiceType())) { |
| | |
| | | serviceOutPath.setSubId(serviceSubtask.getId()); |
| | | iServiceOutPathService.insertServiceOutPath(serviceOutPath); |
| | | //转成16进制 |
| | | format = String.format("%03X", serviceOutPath.getId()); |
| | | format = ShortCode.encode(serviceOutPath.getId()); |
| | | serviceOutPath.setRadix(format); |
| | | serviceOutPath.setUpdateTime(new Date()); |
| | | iServiceOutPathService.updateServiceOutPath(serviceOutPath); |
| | |
| | | * @return 结果 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public String addSubTaskAgain(ServiceSubtask serviceSubtask) { |
| | | ServiceSubtask serviceSubtask1 = selectServiceSubtaskById(serviceSubtask.getId()); |
| | | PatMedInhosp patMedInhosp = new PatMedInhosp(); |
| | |
| | | PatArchive patArchive = taskArchivePort.findByPatid(serviceSubtask1.getPatid()); |
| | | //景宁 新增再次随访不能触发患者再入院 |
| | | boolean inhospAgain = true; |
| | | if (serviceSubtask1.getOrgid().equals("47255004333112711A1001")) { |
| | | if ("47255004333112711A1001".equals(serviceSubtask1.getOrgid())) { |
| | | inhospAgain = false; |
| | | } |
| | | if (patMedInhospList.size() > 0 && inhospAgain) { |
| | |
| | | serviceSubtask1.setRemark(StringUtils.isNotEmpty(serviceSubtask.getRemark()) ? serviceSubtask.getRemark() + ";无需再次随访,任务被标记不在执行" : "无需再次随访,任务被标记不在执行"); |
| | | serviceSubtaskMapper.updateServiceSubtask(serviceSubtask1); |
| | | return "无需再次随访,任务被标记不在执行"; |
| | | } else if (StringUtils.isNotEmpty(patArchive.getNotrequiredFlag()) && patArchive.getNotrequiredFlag().equals("1")) { |
| | | } else if (patArchive != null && StringUtils.isNotEmpty(patArchive.getNotrequiredFlag()) && patArchive.getNotrequiredFlag().equals("1")) { |
| | | serviceSubtask1.setIsVisitAgain(2); |
| | | serviceSubtaskMapper.updateServiceSubtask(serviceSubtask1); |
| | | serviceSubtask1.setRemark(StringUtils.isNotEmpty(serviceSubtask.getRemark()) ? serviceSubtask.getRemark() + ";无需再次随访,患者被标记不需要被服务" : "无需再次随访,患者被标记不需要被服务"); |
| | | return "无需再次随访,患者被标记不需要被服务"; |
| | | } else if (ObjectUtils.isNotEmpty(serviceSubtask1.getIsVisitAgain()) && serviceSubtask1.getIsVisitAgain() == 2) { |
| | | return "无需再次随访,该任务已经再次随访过了"; |
| | | } |
| | | //丽水中医院根据下次任务ID生成再次随访 |
| | | if ("47231022633110211A2101".equals(serviceSubtask1.getOrgid())) { |
| | | return addSubTaskAgainNextTaskId(serviceSubtask1, serviceSubtask); |
| | | } |
| | | //新增再次随访任务 |
| | | ServiceSubtask serviceSubtask2 = DtoConversionUtils.sourceToTarget(serviceSubtask, ServiceSubtask.class); |
| | |
| | | serviceSubtask2.setVisitType(serviceSubtask.getVisitType()); |
| | | serviceSubtask2.setVisitDeptCode(serviceSubtask.getVisitDeptCode()); |
| | | serviceSubtask2.setVisitDeptName(serviceSubtask.getVisitDeptName()); |
| | | // 口径(2026-10-29 业务确认):随访病区一律取出院记录的 |
| | | // ① 再次随访是从请求 DTO 复制出来的,出院记录ID 不在请求里 → 先用原任务的上; |
| | | // ② 再按出院记录把科室/病区/院区统一过来(取不到就保持不变)。 |
| | | if (serviceSubtask2.getInhospid() == null) { |
| | | serviceSubtask2.setInhospid(serviceSubtask1.getInhospid()); |
| | | } |
| | | alignDischargeWardFromInhosp(serviceSubtask2); |
| | | int i = serviceSubtaskMapper.insertServiceSubtask(serviceSubtask2); |
| | | if (i == 1) { |
| | | //新增成功后,需要将之前的任务设置为不要再随访(防止重复点,然后重复生成) |
| | |
| | | return "再次随访任务创建成功"; |
| | | } |
| | | return "再次随访任务创建失败,再请次重试"; |
| | | } |
| | | |
| | | public String addSubTaskAgainNextTaskId(ServiceSubtask serviceSubtask, ServiceSubtask request) { |
| | | if (ObjectUtils.isEmpty(serviceSubtask.getTaskid())) { |
| | | log.error("再次随访任务创建失败,缺少TaskId, 再请次重试"); |
| | | return "再次随访任务创建失败,缺少TaskId, 再请次重试"; |
| | | } |
| | | // 同一原任务串行生成,防止并发重复插入 |
| | | String lockToken = tryLockAgainTask(serviceSubtask.getId()); |
| | | if (lockToken == null) { |
| | | return "再次随访任务正在创建中,请勿重复提交"; |
| | | } |
| | | try { |
| | | ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid()); |
| | | if (ObjectUtils.isEmpty(serviceTask)) { |
| | | log.error("再次随访任务创建失败,找不到对应任务 taskid = {}, 再请次重试", serviceSubtask.getTaskid()); |
| | | return "再次随访任务创建失败,找不到对应任务 taskid = " + serviceSubtask.getTaskid() + ", 再请次重试"; |
| | | } |
| | | // nexttaskflag=0 表示任务链终止,不允许再生成 |
| | | // if ("0".equals(serviceTask.getNexttaskflag())) { |
| | | // log.info("无需再次随访,任务已标记无后续随访, taskid = {}", serviceSubtask.getTaskid()); |
| | | // return "无需再次随访,该任务无后续随访安排"; |
| | | // } |
| | | // 幂等校验:同一原任务只允许生成一条二次随访 |
| | | ServiceSubtaskEntity againQuery = new ServiceSubtaskEntity(); |
| | | againQuery.setUpid(serviceSubtask.getId().intValue()); |
| | | if (CollectionUtils.isNotEmpty(serviceSubtaskMapper.selectServiceSubtaskList(againQuery))) { |
| | | return "无需再次随访,该任务已经再次随访过了"; |
| | | } |
| | | // 查询出院记录 |
| | | PatMedInhosp patMedInhospVo = new PatMedInhosp(); |
| | | patMedInhospVo.setOrgid(serviceSubtask.getOrgid()); |
| | | patMedInhospVo.setInhospid(serviceSubtask.getInhospid()); |
| | | patMedInhospVo.setCry(1); |
| | | List<PatMedInhosp> patMedInhospList = inhospVisitFacade.selectPatMedInhospList(patMedInhospVo); |
| | | PatMedInhosp patMedInhosp = new PatMedInhosp(); |
| | | if (CollectionUtils.isNotEmpty(patMedInhospList)) { |
| | | patMedInhosp = patMedInhospList.get(0); |
| | | } else { |
| | | log.error("再次随访任务创建失败,任务缺少 对应的出院记录, taskid = {}, 再请次重试", serviceSubtask.getTaskid()); |
| | | return "再次随访任务创建失败,任务缺少 对应的出院记录, taskid = " + serviceSubtask.getTaskid() + ", 再请次重试"; |
| | | } |
| | | // 未配置 nexttaskid 时复用当前任务配置,防止历史任务直接失败 |
| | | Long nexttaskid = serviceTask.getNexttaskid(); |
| | | if (ObjectUtils.isEmpty(nexttaskid) || Long.valueOf(0L).equals(nexttaskid)) { |
| | | nexttaskid = serviceTask.getTaskid(); |
| | | log.warn("再次随访任务未配置 nexttaskid,按当前任务配置生成, taskid = {}", nexttaskid); |
| | | } |
| | | ServiceTask nextServiceTask = serviceTaskService.selectServiceTaskByTaskid(nexttaskid); |
| | | if (ObjectUtils.isEmpty(nextServiceTask)) { |
| | | log.error("再次随访任务创建失败,找不到对应任务 nexttaskid = {}, 再请次重试", nexttaskid); |
| | | return "再次随访任务创建失败,找不到对应任务 nexttaskid = " + nexttaskid + ", 再请次重试"; |
| | | } |
| | | PatArchive patArchive = taskArchivePort.findByPatid(serviceSubtask.getPatid()); |
| | | if (ObjectUtils.isEmpty(patArchive)) { |
| | | log.error("再次随访任务创建失败,未找到患者档案 patid = {}, subTaskId = {}", serviceSubtask.getPatid(), serviceSubtask.getId()); |
| | | return "再次随访任务创建失败,未找到患者档案, subTaskId = " + serviceSubtask.getId() + ", 再请次重试"; |
| | | } |
| | | String config = sysConfigService.selectConfigByKey("visit.early.day"); |
| | | Integer type = 1; |
| | | String resource = resolveAgainResource(request); |
| | | if (StringUtils.isEmpty(resource)) { |
| | | String requestResource = request == null ? null : request.getResource(); |
| | | log.error("再次随访任务创建失败,再次随访方式不合法, resource = {}, subTaskId = {}", requestResource, serviceSubtask.getId()); |
| | | return "再次随访任务创建失败,再次随访方式不合法, resource = " + requestResource + ", 再请次重试"; |
| | | } |
| | | String visitDeptCode; |
| | | String visitDeptName; |
| | | if ("1".equals(resource)) { |
| | | // 本科室随访:请求科室优先,未传时使用原任务出院科室;不依赖原任务 visitType |
| | | String requestDeptCode = firstNonBlank(request == null ? null : request.getVisitDeptCode()); |
| | | String requestDeptName = firstNonBlank(request == null ? null : request.getVisitDeptName()); |
| | | if (StringUtils.isNotEmpty(requestDeptCode)) { |
| | | visitDeptCode = requestDeptCode; |
| | | visitDeptName = requestDeptName; |
| | | } else { |
| | | // 出院科室:service_subtask 里对应的是 leaveldeptcode(deptcode 已改为在院科室) |
| | | visitDeptCode = serviceSubtask.getLeaveldeptcode(); |
| | | visitDeptName = serviceSubtask.getLeaveldeptname(); |
| | | } |
| | | if (StringUtils.isEmpty(visitDeptCode)) { |
| | | log.error("再次随访任务创建失败,本科室随访未找到随访科室, subTaskId = {}", serviceSubtask.getId()); |
| | | return "再次随访任务创建失败,本科室随访未找到随访科室, subTaskId = " + serviceSubtask.getId() + ", 再请次重试"; |
| | | } |
| | | SysDept ownDept = selectDeptByCode(serviceSubtask.getOrgid(), visitDeptCode); |
| | | if (ObjectUtils.isNotEmpty(ownDept)) { |
| | | visitDeptCode = ownDept.getDeptCode(); |
| | | visitDeptName = ownDept.getDeptName(); |
| | | } else if (StringUtils.isNotEmpty(requestDeptCode) || StringUtils.isEmpty(visitDeptName)) { |
| | | log.error("再次随访任务创建失败,本科室随访科室不存在, deptCode = {}, subTaskId = {}", visitDeptCode, serviceSubtask.getId()); |
| | | return "再次随访任务创建失败,本科室随访科室不存在, deptCode = " + visitDeptCode + ", 再请次重试"; |
| | | } |
| | | } else { |
| | | // 随访中心科室随访 |
| | | String centerDept = sysConfigService.selectConfigByKey("sys.smartor.center.dept", serviceSubtask.getOrgid()); |
| | | if (ObjectUtils.isEmpty(centerDept)) { |
| | | log.error("再次随访任务创建失败,未配置随访中心科室参数 sys.smartor.center.dept ,subTaskId = {}, 再请次重试", serviceSubtask.getId()); |
| | | return "再次随访任务创建失败,未配置随访中心科室参数 sys.smartor.center.dept ,subTaskId = " + serviceSubtask.getId() + ", 再请次重试"; |
| | | } |
| | | SysDept sysDeptVo = new SysDept(); |
| | | sysDeptVo.setDeptType("1"); |
| | | sysDeptVo.setOrgid(serviceSubtask.getOrgid()); |
| | | sysDeptVo.setDeptCode(centerDept); |
| | | List<SysDept> sysDepts = sysDeptService.selectDeptList(sysDeptVo); |
| | | SysDept sysDept = new SysDept(); |
| | | if (CollectionUtils.isNotEmpty(sysDepts)) { |
| | | sysDept = sysDepts.get(0); |
| | | } else { |
| | | log.error("再次随访任务创建失败,未找到参数 sys.smartor.center.dept 配置的随访中心科室 deptCode = {}, 再请次重试", centerDept); |
| | | return "再次随访任务创建失败,未找到参数 sys.smartor.center.dept 配置的随访中心科室 deptCode = " + centerDept + ", 再请次重试"; |
| | | } |
| | | visitDeptCode = sysDept.getDeptCode(); |
| | | visitDeptName = sysDept.getDeptName(); |
| | | } |
| | | |
| | | // 按下一任务生成二次随访子任务(表单、发送方式取自下一任务); |
| | | // 组装逻辑已在本模块的 OperationSubtaskWriter 里(原 IPatMedInhospService#boxedServiceSubtask),这里直接复用 |
| | | ServiceSubtask serviceSubtaskAgain = operationSubtaskWriter.boxedServiceSubtask(nextServiceTask, |
| | | patMedInhosp, patArchive, config, type); |
| | | if (ObjectUtils.isEmpty(serviceSubtaskAgain) || ObjectUtils.isEmpty(serviceSubtaskAgain.getTaskid())) { |
| | | log.error("再次随访任务创建失败,下一任务生成子任务为空, subTaskId = {}, nextTaskId = {}", serviceSubtask.getId(), nexttaskid); |
| | | return "再次随访任务创建失败,患者出院记录不完整或下一任务配置异常, 再请次重试"; |
| | | } |
| | | Date now = new Date(); |
| | | applyAgainVisitTime(serviceSubtaskAgain, serviceSubtask, nextServiceTask, config, now); |
| | | // 医生处理意见取自本次请求,按下一任务重建子任务时需显式带入,否则不会落库 |
| | | if (request != null) { |
| | | serviceSubtaskAgain.setDoctorOpinion(request.getDoctorOpinion()); |
| | | serviceSubtaskAgain.setRemark(request.getRemark()); |
| | | serviceSubtaskAgain.setDrcode(request.getDrcode()); |
| | | serviceSubtaskAgain.setDrname(request.getDrname()); |
| | | serviceSubtaskAgain.setNurseId(request.getNurseId()); |
| | | serviceSubtaskAgain.setNurseName(request.getNurseName()); |
| | | serviceSubtaskAgain.setManagementDoctor(request.getManagementDoctor()); |
| | | serviceSubtaskAgain.setManagementDoctorCode(request.getManagementDoctorCode()); |
| | | } |
| | | serviceSubtaskAgain.setId(null); |
| | | serviceSubtaskAgain.setUpid(serviceSubtask.getId().intValue()); |
| | | serviceSubtaskAgain.setVisitCount(ObjectUtils.isNotEmpty(serviceSubtask.getVisitCount()) |
| | | ? serviceSubtask.getVisitCount() + 1 : 2); |
| | | serviceSubtaskAgain.setSendstate(2L); |
| | | serviceSubtaskAgain.setIsVisitAgain(1); |
| | | serviceSubtaskAgain.setCreateTime(now); |
| | | serviceSubtaskAgain.setUpdateTime(now); |
| | | // 新任务的随访方式记录本次选择的再次随访方式;resource 本身不落库 |
| | | serviceSubtaskAgain.setVisitType(resource); |
| | | serviceSubtaskAgain.setVisitDeptCode(visitDeptCode); |
| | | serviceSubtaskAgain.setVisitDeptName(visitDeptName); |
| | | int i = serviceSubtaskMapper.insertServiceSubtask(serviceSubtaskAgain); |
| | | if (i != 1) { |
| | | return "再次随访任务创建失败,再请次重试"; |
| | | } |
| | | // 初始化发送方式明细,补偿(compensateTask)和超时判断依赖该表 |
| | | addServiceSubtaskPreachform(serviceSubtaskAgain, null); |
| | | // 条件更新原任务:0 行说明已被其他请求生成,抛异常回滚本次插入的重复数据 |
| | | int updated = serviceSubtaskMapper.updateIsVisitAgainById(serviceSubtask.getId(), 2); |
| | | if (updated != 1) { |
| | | throw new BaseException("无需再次随访,该任务已经再次随访过了"); |
| | | } |
| | | return "再次随访任务创建成功"; |
| | | } finally { |
| | | unlockAgainTask(serviceSubtask.getId(), lockToken); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 解析再次随访方式:仅取请求参数 resource,不使用原任务 visitType;未传时默认随访中心。 |
| | | * |
| | | * @return 1-本科室随访;2-随访中心随访;null-非法值 |
| | | */ |
| | | private String resolveAgainResource(ServiceSubtask request) { |
| | | String resource = request == null ? null : request.getResource(); |
| | | if (StringUtils.isEmpty(resource) || StringUtils.isEmpty(resource.trim())) { |
| | | return "2"; |
| | | } |
| | | resource = resource.trim(); |
| | | return ("1".equals(resource) || "2".equals(resource)) ? resource : null; |
| | | } |
| | | |
| | | private String firstNonBlank(String... values) { |
| | | if (values == null) { |
| | | return null; |
| | | } |
| | | for (String value : values) { |
| | | if (StringUtils.isNotEmpty(value) && StringUtils.isNotEmpty(value.trim())) { |
| | | return value.trim(); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 按 orgid + deptCode 查询启用科室,查不到返回 null。 |
| | | */ |
| | | private SysDept selectDeptByCode(String orgid, String deptCode) { |
| | | if (StringUtils.isEmpty(orgid) || StringUtils.isEmpty(deptCode)) { |
| | | return null; |
| | | } |
| | | SysDept query = new SysDept(); |
| | | query.setDeptType("1"); |
| | | query.setOrgid(orgid); |
| | | query.setDeptCode(deptCode); |
| | | List<SysDept> deptList = sysDeptService.selectDeptList(query); |
| | | return CollectionUtils.isEmpty(deptList) ? null : deptList.get(0); |
| | | } |
| | | |
| | | /** |
| | | * 二次随访时间:首次随访完成时间(未完成取当前时间)+ 下一任务发送天数,过去时间兜底为当前时间。 |
| | | * 通知类任务按 visit.early.day 提前发送。 |
| | | */ |
| | | private void applyAgainVisitTime(ServiceSubtask target, ServiceSubtask origin, ServiceTask nextTask, |
| | | String earlyDayConfig, Date now) { |
| | | if (target == null || origin == null || nextTask == null || now == null) { |
| | | return; |
| | | } |
| | | Date baseTime = ObjectUtils.isNotEmpty(origin.getFinishtime()) ? origin.getFinishtime() : now; |
| | | int intervalDays = ObjectUtils.isNotEmpty(nextTask.getSendDay()) ? nextTask.getSendDay().intValue() : 0; |
| | | Date dueTime = addDays(baseTime, intervalDays); |
| | | if (dueTime.before(now)) { |
| | | dueTime = now; |
| | | } |
| | | if(ObjectUtils.isNotEmpty(origin.getVisitTime())){ |
| | | dueTime = ObjectUtils.isNotEmpty(origin.getVisitTime()) ? origin.getVisitTime() : now; |
| | | } |
| | | |
| | | target.setLongSendTime(dueTime); |
| | | Date visitTime = dueTime; |
| | | if ("3".equals(nextTask.getType())) { |
| | | visitTime = addDays(dueTime, -parseIntConfig(earlyDayConfig)); |
| | | if (visitTime.before(now)) { |
| | | visitTime = now; |
| | | } |
| | | } |
| | | target.setVisitTime(visitTime); |
| | | } |
| | | |
| | | private int parseIntConfig(String value) { |
| | | if (StringUtils.isEmpty(value)) { |
| | | return 0; |
| | | } |
| | | try { |
| | | return Integer.parseInt(value.trim()); |
| | | } catch (NumberFormatException e) { |
| | | log.warn("配置值不是合法整数,按0处理, value={}", value); |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 日期加减天数(days 可为负)。 |
| | | * <p> |
| | | * 说明:本模块其它地方用的是 {@code DateUtils.addDays},但 ruoyi-common 的 DateUtils 并未提供该方法, |
| | | * 这里是二次随访时间计算自带的私有实现,避免依赖不存在的工具方法。 |
| | | */ |
| | | private Date addDays(Date date, int days) { |
| | | if (date == null) { |
| | | return null; |
| | | } |
| | | Calendar calendar = Calendar.getInstance(); |
| | | calendar.setTime(date); |
| | | calendar.add(Calendar.DAY_OF_MONTH, days); |
| | | return calendar.getTime(); |
| | | } |
| | | |
| | | /** |
| | | * 首次随访任务超时后,按下一任务自动生成再次随访任务。 |
| | | * <p> |
| | | * 定时任务入口 {@code RyTask#dealSubtaskDeadline} 使用。与手动入口一致的部分: |
| | | * 表单/发送方式取下一任务、链路 upid + visit_count、发送方式明细初始化; |
| | | * 差异:超时再次随访固定走随访中心科室(随访中心未配置时回退原任务科室),原任务置为发送失败(sendstate=5)。 |
| | | * <p> |
| | | * 幂等:同一原任务只允许生成一条再次随访(Redis 锁 + upid 校验 + 原任务条件更新), |
| | | * 多节点/多线程同时扫描时,后到的事务会在条件更新处拿到 0 行并回滚本次插入。 |
| | | * |
| | | * @param serviceSubtask 已超时的首次随访子任务 |
| | | * @return 结果描述 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public String addSubTaskAgainByDeadline(ServiceSubtask serviceSubtask) { |
| | | if (ObjectUtils.isEmpty(serviceSubtask) || ObjectUtils.isEmpty(serviceSubtask.getId())) { |
| | | return "再次随访任务创建失败,原任务不存在"; |
| | | } |
| | | String lockToken = tryLockAgainTask(serviceSubtask.getId()); |
| | | if (lockToken == null) { |
| | | return "再次随访任务正在创建中,请勿重复提交"; |
| | | } |
| | | try { |
| | | // 幂等校验:同一原任务只允许生成一条再次随访 |
| | | ServiceSubtaskEntity againQuery = new ServiceSubtaskEntity(); |
| | | againQuery.setUpid(serviceSubtask.getId().intValue()); |
| | | if (CollectionUtils.isNotEmpty(serviceSubtaskMapper.selectServiceSubtaskList(againQuery))) { |
| | | return "无需再次随访,该任务已经再次随访过了"; |
| | | } |
| | | // 原任务已被标记为不再随访(手动入口写过 is_visit_again=2) |
| | | if (ObjectUtils.isNotEmpty(serviceSubtask.getIsVisitAgain()) && serviceSubtask.getIsVisitAgain() == 2) { |
| | | return "无需再次随访,该任务已经再次随访过了"; |
| | | } |
| | | ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid()); |
| | | if (ObjectUtils.isEmpty(serviceTask)) { |
| | | return "再次随访任务创建失败,找不到对应任务 taskid = " + serviceSubtask.getTaskid(); |
| | | } |
| | | if ("0".equals(serviceTask.getNexttaskflag())) { |
| | | return "无需再次随访,该任务无后续随访安排"; |
| | | } |
| | | if (ObjectUtils.isEmpty(serviceTask.getNexttaskid())) { |
| | | return "再次随访任务创建失败,任务缺少 nexttaskid, taskid = " + serviceSubtask.getTaskid(); |
| | | } |
| | | ServiceTask nextServiceTask = serviceTaskService.selectServiceTaskByTaskid(serviceTask.getNexttaskid()); |
| | | if (ObjectUtils.isEmpty(nextServiceTask)) { |
| | | return "再次随访任务创建失败,找不到对应任务 nexttaskid = " + serviceTask.getNexttaskid(); |
| | | } |
| | | PatMedInhosp patMedInhosp = selectOutHospRecord(serviceSubtask); |
| | | if (ObjectUtils.isEmpty(patMedInhosp)) { |
| | | return "再次随访任务创建失败,任务缺少对应的出院记录, taskid = " + serviceSubtask.getTaskid(); |
| | | } |
| | | PatArchive patArchive = taskArchivePort.findByPatid(serviceSubtask.getPatid()); |
| | | if (ObjectUtils.isEmpty(patArchive)) { |
| | | return "再次随访任务创建失败,未找到患者档案, subTaskId = " + serviceSubtask.getId(); |
| | | } |
| | | String config = sysConfigService.selectConfigByKey("visit.early.day"); |
| | | String visitDeptCode; |
| | | String visitDeptName; |
| | | // 随访中心科室:未配置(或科室不存在)时回退原任务科室,保持超时入口原有行为 |
| | | SysDept centerDept = selectCenterDept(serviceSubtask.getOrgid()); |
| | | if (ObjectUtils.isNotEmpty(centerDept)) { |
| | | visitDeptCode = centerDept.getDeptCode(); |
| | | visitDeptName = centerDept.getDeptName(); |
| | | } else { |
| | | String fallbackDeptCode = firstNonBlank(serviceSubtask.getVisitDeptCode()); |
| | | if (StringUtils.isNotEmpty(fallbackDeptCode)) { |
| | | visitDeptCode = fallbackDeptCode; |
| | | visitDeptName = serviceSubtask.getVisitDeptName(); |
| | | } else { |
| | | // 出院科室:service_subtask 里对应的是 leaveldeptcode(deptcode 已改为在院科室) |
| | | visitDeptCode = serviceSubtask.getLeaveldeptcode(); |
| | | visitDeptName = serviceSubtask.getLeaveldeptname(); |
| | | } |
| | | } |
| | | // 组装逻辑复用本模块 OperationSubtaskWriter(原 IPatMedInhospService#boxedServiceSubtask) |
| | | ServiceSubtask serviceSubtaskAgain = operationSubtaskWriter.boxedServiceSubtask(nextServiceTask, |
| | | patMedInhosp, patArchive, config, 1); |
| | | if (ObjectUtils.isEmpty(serviceSubtaskAgain) || ObjectUtils.isEmpty(serviceSubtaskAgain.getTaskid())) { |
| | | return "再次随访任务创建失败,患者出院记录不完整或下一任务配置异常, subTaskId = " + serviceSubtask.getId(); |
| | | } |
| | | Date now = new Date(); |
| | | serviceSubtaskAgain.setId(null); |
| | | serviceSubtaskAgain.setUpid(serviceSubtask.getId().intValue()); |
| | | serviceSubtaskAgain.setVisitCount(ObjectUtils.isNotEmpty(serviceSubtask.getVisitCount()) |
| | | ? serviceSubtask.getVisitCount() + 1 : 2); |
| | | serviceSubtaskAgain.setSendstate(2L); |
| | | serviceSubtaskAgain.setIsVisitAgain(1); |
| | | serviceSubtaskAgain.setCreateTime(now); |
| | | serviceSubtaskAgain.setUpdateTime(now); |
| | | // 超时再次随访固定为随访中心随访 |
| | | serviceSubtaskAgain.setVisitType("2"); |
| | | serviceSubtaskAgain.setVisitDeptCode(visitDeptCode); |
| | | serviceSubtaskAgain.setVisitDeptName(visitDeptName); |
| | | if (serviceSubtaskMapper.insertServiceSubtask(serviceSubtaskAgain) != 1) { |
| | | return "再次随访任务创建失败,再请次重试"; |
| | | } |
| | | // 初始化发送方式明细,补偿(compensateTask)和超时判断依赖该表 |
| | | addServiceSubtaskPreachform(serviceSubtaskAgain, null); |
| | | // 条件更新原任务:0 行说明已被其他请求生成,抛异常回滚本次插入的重复数据 |
| | | if (serviceSubtaskMapper.updateIsVisitAgainById(serviceSubtask.getId(), 2) != 1) { |
| | | throw new BaseException("无需再次随访,该任务已经再次随访过了"); |
| | | } |
| | | // 原任务超时,置为发送失败 |
| | | ServiceSubtask originUpdate = new ServiceSubtask(); |
| | | originUpdate.setId(serviceSubtask.getId()); |
| | | originUpdate.setSendstate(5L); |
| | | originUpdate.setUpdateTime(now); |
| | | serviceSubtaskMapper.updateServiceSubtask(originUpdate); |
| | | return "再次随访任务创建成功"; |
| | | } finally { |
| | | unlockAgainTask(serviceSubtask.getId(), lockToken); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 按 orgid + inhospid 取出院记录(超时再次随访用)。 |
| | | */ |
| | | private PatMedInhosp selectOutHospRecord(ServiceSubtask serviceSubtask) { |
| | | PatMedInhosp patMedInhospVo = new PatMedInhosp(); |
| | | patMedInhospVo.setOrgid(serviceSubtask.getOrgid()); |
| | | patMedInhospVo.setInhospid(serviceSubtask.getInhospid()); |
| | | patMedInhospVo.setCry(1); |
| | | List<PatMedInhosp> patMedInhospList = inhospVisitFacade.selectPatMedInhospList(patMedInhospVo); |
| | | if (CollectionUtils.isEmpty(patMedInhospList)) { |
| | | return null; |
| | | } |
| | | return patMedInhospList.get(0); |
| | | } |
| | | |
| | | /** |
| | | * 取随访中心科室,未配置或科室不存在时返回 null(由调用方回退原任务科室)。 |
| | | */ |
| | | private SysDept selectCenterDept(String orgid) { |
| | | String centerDept = sysConfigService.selectConfigByKey("sys.smartor.center.dept", orgid); |
| | | if (StringUtils.isEmpty(centerDept)) { |
| | | return null; |
| | | } |
| | | SysDept sysDeptVo = new SysDept(); |
| | | sysDeptVo.setDeptType("1"); |
| | | sysDeptVo.setOrgid(orgid); |
| | | sysDeptVo.setDeptCode(centerDept); |
| | | // 定时任务线程没有登录上下文,不能调用带 @DataScope 的 selectDeptList,否则会抛“获取用户信息异常”。 |
| | | List<SysDept> sysDepts = sysDeptService.selectSysDeptList(sysDeptVo); |
| | | return CollectionUtils.isEmpty(sysDepts) ? null : sysDepts.get(0); |
| | | } |
| | | |
| | | /** |
| | | * 获取二次随访生成锁。Redis 异常时降级为数据库幂等校验,不阻断业务。 |
| | | */ |
| | | private String tryLockAgainTask(Long subId) { |
| | | String token = UUID.randomUUID().toString(); |
| | | try { |
| | | Boolean locked = redisCache.setCacheObjectIfAbsent( |
| | | AGAIN_LOCK_KEY_PREFIX + subId, token, AGAIN_LOCK_SECONDS, TimeUnit.SECONDS); |
| | | return Boolean.TRUE.equals(locked) ? token : null; |
| | | } catch (Exception e) { |
| | | log.warn("再次随访获取Redis锁失败,降级使用数据库幂等校验, subId={}", subId, e); |
| | | return token; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 释放二次随访生成锁,仅释放自己持有的锁。 |
| | | */ |
| | | private void unlockAgainTask(Long subId, String token) { |
| | | if (token == null) { |
| | | return; |
| | | } |
| | | try { |
| | | String lockKey = AGAIN_LOCK_KEY_PREFIX + subId; |
| | | Object current = redisCache.getCacheObject(lockKey); |
| | | if (token.equals(current)) { |
| | | redisCache.deleteObject(lockKey); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("再次随访释放Redis锁失败, subId={}", subId, e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 按任务的 preachformDesc 初始化发送方式明细(service_subtask_preachform)。 |
| | | * <p> |
| | | * 迁移说明:原实现是 smartor 侧 {@code IPatMedInhospService#addServiceSubtaskPreachform}, |
| | | * 读写的两张表都属于本模块,故按模块边界搬到这里,语义保持一致: |
| | | * 先按 subid+preachform 查重(已存在就不新增),sendstate 默认为 "1"(传参可覆盖)。 |
| | | * |
| | | * @param serviceSubtask 已落库的子任务 |
| | | * @param sendstate 发送状态覆盖值,为空则用 "1" |
| | | * @return 固定返回 0(与原实现一致) |
| | | */ |
| | | private Integer addServiceSubtaskPreachform(ServiceSubtask serviceSubtask, String sendstate) { |
| | | ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid()); |
| | | if (serviceTask == null) { |
| | | log.error("addServiceSubtaskPreachform taskid={} 查不到任务,跳过preachform子表生成", serviceSubtask.getTaskid()); |
| | | return 0; |
| | | } |
| | | String preachformDesc = serviceTask.getPreachformDesc(); |
| | | if (StringUtils.isEmpty(preachformDesc)) { |
| | | return 0; |
| | | } |
| | | List<ServiceSubtaskPreachform> preachformList; |
| | | try { |
| | | preachformList = JSON.parseArray(preachformDesc, ServiceSubtaskPreachform.class); |
| | | } catch (Exception e) { |
| | | log.error("preachform转List<ServiceSubtaskPreachform>报错了:{}", e.getMessage()); |
| | | return 0; |
| | | } |
| | | if (CollectionUtils.isEmpty(preachformList)) { |
| | | log.error("preachformDesc解析后为空,跳过preachform子表生成,taskid为:{},preachformDesc为:{}", |
| | | serviceSubtask.getTaskid(), preachformDesc); |
| | | return 0; |
| | | } |
| | | for (ServiceSubtaskPreachform serviceSubtaskPreachform : preachformList) { |
| | | serviceSubtaskPreachform.setTaskid(serviceSubtask.getTaskid()); |
| | | serviceSubtaskPreachform.setSubid(serviceSubtask.getId()); |
| | | //先查询一下,是否存在,如果已经存在,就不新增了 |
| | | if (CollectionUtils.isNotEmpty(taskPreachformPort.selectServiceSubtaskPreachformList(serviceSubtaskPreachform))) { |
| | | continue; |
| | | } |
| | | serviceSubtaskPreachform.setSendstate(StringUtils.isNotEmpty(sendstate) ? sendstate : "1"); |
| | | serviceSubtaskPreachform.setOrgid(serviceSubtask.getOrgid()); |
| | | taskPreachformPort.insertServiceSubtaskPreachform(serviceSubtaskPreachform); |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 科室编码去重,保持原顺序,避免 service_task.deptcode 超长和 service_taskdept 重复绑定 |
| | | */ |
| | | private String normalizeDeptCodes(String deptCodes) { |
| | | if (StringUtils.isEmpty(deptCodes)) { |
| | | return deptCodes; |
| | | } |
| | | return Arrays.stream(deptCodes.split(",")) |
| | | .map(String::trim) |
| | | .filter(code -> StringUtils.isNotEmpty(code)) |
| | | .distinct() |
| | | .collect(Collectors.joining(",")); |
| | | } |
| | | |
| | | /** |
| | | * 取逗号串里的第一个值(任务上科室/病区是多选,而 {@code service_subtask} 上都是单值列)。 |
| | | */ |
| | | private String firstValue(String value) { |
| | | if (StringUtils.isEmpty(value)) { |
| | | return null; |
| | | } |
| | | for (String item : value.split(",")) { |
| | | if (StringUtils.isNotEmpty(item.trim())) { |
| | | return item.trim(); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 任务配置({@link ServiceTaskVO})→ 子任务({@link ServiceSubtask})的字段搬运。 |
| | | * <p> |
| | | * <b>为什么不能直接 {@code DtoConversionUtils.sourceToTarget(serviceTaskVO, ServiceSubtask.class)}:</b> |
| | | * 它是纯同名拷贝,而两边同名的 {@code deptcode / deptname} <b>语义不同</b>: |
| | | * <ul> |
| | | * <li>{@code ServiceTaskVO.deptcode / deptname} = 任务的「适用科室」, |
| | | * 出院随访(serviceType=2)/ 出院宣教(4)/ 门诊随访(3)等场景下就是<b>离院科室</b>;</li> |
| | | * <li>{@code ServiceSubtask.deptcode / deptname} = <b>在院科室</b> |
| | | * (2026-11 由原 sendlimitpbegin/pend 复用而来,见 {@code ServiceSubtask} 字段注释)。</li> |
| | | * </ul> |
| | | * 于是纯同名拷贝会把「离院科室」写进「在院科室」列,后果有两个: |
| | | * <ol> |
| | | * <li>按在院维度筛({@code deptcodes} → {@code service_subtask.deptcode})会误命中这些子任务;</li> |
| | | * <li>按离院维度筛({@code leaveldeptcodes} → {@code service_subtask.leaveldeptcode})时, |
| | | * 患者自己没带科室的那些(前端手动勾选患者很常见)会因为 {@code leaveldeptcode} 为空而查不到。</li> |
| | | * </ol> |
| | | * 因此这里按项目既定口径({@code doc/service_subtask_字段改名_改动清单.md} §8:这几条 |
| | | * 「长期任务 / 任务组 / 手动建任务」路径<b>不填在院维度</b>)统一处理: |
| | | * <b>在院维度 4 列清空</b>(在院科室/在院病区只有 HIS 链路能从 {@code pat_med_inhosp} 拿到), |
| | | * <b>科室 / 病区一律落离院维度</b>({@code leaveldeptcode/name}、{@code leavehospitaldistrictcode/name})。 |
| | | * |
| | | * @param serviceTaskVO 建任务入参 |
| | | * @return 已按离院维度归位的子任务对象(在院维度为空) |
| | | */ |
| | | private ServiceSubtask toSubtaskFromTaskVO(ServiceTaskVO serviceTaskVO) { |
| | | ServiceSubtask serviceSubtask = DtoConversionUtils.sourceToTarget(serviceTaskVO, ServiceSubtask.class); |
| | | // ① 在院维度必须清空:任务配置的科室不是患者本人的在院科室 |
| | | serviceSubtask.setDeptcode(null); |
| | | serviceSubtask.setDeptname(null); |
| | | serviceSubtask.setHospitaldistrictcode(null); |
| | | serviceSubtask.setHospitaldistrictname(null); |
| | | // ② 科室 / 病区 → 离院维度(多选串取第一个,子任务是单值列) |
| | | serviceSubtask.setLeaveldeptcode(firstValue(serviceTaskVO.getDeptcode())); |
| | | serviceSubtask.setLeaveldeptname(firstValue(serviceTaskVO.getDeptname())); |
| | | serviceSubtask.setLeavehospitaldistrictcode(firstValue(serviceTaskVO.getLeavehospitaldistrictcode())); |
| | | serviceSubtask.setLeavehospitaldistrictname(firstValue(serviceTaskVO.getLeavehospitaldistrictname())); |
| | | return serviceSubtask; |
| | | } |
| | | |
| | | /** |
| | | * 单一任务 |
| | | * |
| | | * @return 结果 |
| | |
| | | log.info("任务入参为空,请检查入参"); |
| | | throw new BaseException("任务入参为空,请检查入参"); |
| | | } |
| | | // 科室编码去重,避免 service_task.deptcode 超长及 service_taskdept 重复绑定 |
| | | serviceTaskVO.setDeptcode(normalizeDeptCodes(serviceTaskVO.getDeptcode())); |
| | | ServiceTask serviceTask = DtoConversionUtils.sourceToTarget(serviceTaskVO, ServiceTask.class); |
| | | if (CollectionUtils.isEmpty(serviceTaskVO.getPreachformList())) throw new BaseException("任务的发送方式为空"); |
| | | //将preachfromList放到serviceTask中的preachfrom中 |
| | |
| | | } |
| | | serviceTask.setLeaveldeptcodes(serviceTaskVO.getLeaveldeptcodes()); |
| | | if (StringUtils.isEmpty(serviceTask.getLeavehospitaldistrictname()) && StringUtils.isNotEmpty(serviceTask.getLeavehospitaldistrictcode())) { |
| | | SysDept sysDept = taskSysDeptPort.selectDeptByCode(serviceTask.getLeavehospitaldistrictcode(), null); |
| | | SysDept sysDept = sysDept2Mapper.selectDeptByCode(serviceTask.getLeavehospitaldistrictcode(), null); |
| | | serviceTask.setLeavehospitaldistrictname(sysDept.getDeptName()); |
| | | } |
| | | if (serviceTaskVO.getLongTask() == 1) serviceTask.setSendState(2L); |
| | |
| | | // } |
| | | |
| | | //将任务信息放到服务表中 |
| | | ServiceSubtask serviceSubtask = DtoConversionUtils.sourceToTarget(serviceTaskVO, ServiceSubtask.class); |
| | | // 科室/病区按「离院维度」归位(出院随访的科室病区就是离院科室/病区),见 toSubtaskFromTaskVO |
| | | ServiceSubtask serviceSubtask = toSubtaskFromTaskVO(serviceTaskVO); |
| | | serviceSubtask.setTaskid(serviceTask.getTaskid().longValue()); |
| | | serviceSubtask.setTemplatename(ObjectUtils.isNotEmpty(tempid) ? tempName : serviceTask.getTemplatename()); |
| | | serviceSubtask.setTemplateid(ObjectUtils.isNotEmpty(tempid) ? tempid : serviceTask.getTemplateid()); |
| | |
| | | serviceSubtask.setInhospid(patTaskRelevance.getHospid()); |
| | | //新建完成就是待发送状态 |
| | | serviceSubtask.setSendstate(2L); |
| | | serviceSubtask.setDeptcode(StringUtils.isNotEmpty(patTaskRelevance.getDeptcode()) ? patTaskRelevance.getDeptcode() : serviceTaskVO.getDeptcode()); |
| | | serviceSubtask.setDeptname(StringUtils.isNotEmpty(patTaskRelevance.getDeptName()) ? patTaskRelevance.getDeptName() : serviceTaskVO.getDeptname()); |
| | | serviceSubtask.setLeavehospitaldistrictcode(patTaskRelevance.getLeavehospitaldistrictcode()); |
| | | serviceSubtask.setLeavehospitaldistrictname(patTaskRelevance.getLeavehospitaldistrictname()); |
| | | // 离院维度(出院随访 = 出院科室 / 出院病区): |
| | | // 患者自带的出院科室/病区优先,患者侧没带(前端手动勾选很常见)时回退到任务配置, |
| | | // 不能无条件覆盖 —— 否则会把 toSubtaskFromTaskVO 按任务配置兜好的离院维度抹成 null, |
| | | // 结果「任务配了科室病区、生成出来的子任务离院科室/病区却是空」。 |
| | | serviceSubtask.setLeaveldeptcode(firstNonBlank(patTaskRelevance.getDeptcode(), |
| | | firstValue(serviceTaskVO.getDeptcode()))); |
| | | serviceSubtask.setLeaveldeptname(firstNonBlank(patTaskRelevance.getDeptName(), |
| | | firstValue(serviceTaskVO.getDeptname()))); |
| | | serviceSubtask.setLeavehospitaldistrictcode(firstNonBlank(patTaskRelevance.getLeavehospitaldistrictcode(), |
| | | firstValue(serviceTaskVO.getLeavehospitaldistrictcode()))); |
| | | serviceSubtask.setLeavehospitaldistrictname(firstNonBlank(patTaskRelevance.getLeavehospitaldistrictname(), |
| | | firstValue(serviceTaskVO.getLeavehospitaldistrictname()))); |
| | | serviceSubtask.setType(serviceTaskVO.getType()); |
| | | serviceSubtask.setHospType(patTaskRelevance.getHospType()); |
| | | serviceSubtask.setOpenid(patTaskRelevance.getOpenid()); |
| | |
| | | ServiceTask serviceTask1 = serviceTaskService.selectServiceTaskByTaskid(serviceTask.getTaskid()); |
| | | ServiceSubtaskEntity serviceSubtaskVO = new ServiceSubtaskEntity(); |
| | | serviceSubtaskVO.setTaskid(serviceTask1.getTaskid()); |
| | | //只删除首次随访未完成的 |
| | | serviceSubtaskVO.setVisitCount(1); |
| | | serviceSubtaskVO.setSendstate(2L); |
| | | List<ServiceSubtask> serviceSubtaskList = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskVO); |
| | | serviceSubtaskVO.setSendstate(1L); |
| | |
| | | if (StringUtils.isNotEmpty(serviceTaskVO.getAppltype())) { |
| | | if (serviceTaskVO.getAppltype().equals("1") || serviceTaskVO.getAppltype().equals("2")) { |
| | | if (StringUtils.isNotEmpty(serviceTaskVO.getDeptcode())) { |
| | | // 修改任务时先软删除旧的科室绑定,避免旧科室残留 |
| | | ServiceTaskdept oldDept = new ServiceTaskdept(); |
| | | oldDept.setTaskId(serviceTask.getTaskid()); |
| | | oldDept.setDeptType("1"); |
| | | serviceTaskdeptService.deleteServiceTaskdeptByTaskId(oldDept); |
| | | String[] deptcodes = serviceTaskVO.getDeptcode().split(","); |
| | | for (String deptCode : deptcodes) { |
| | | ServiceTaskdept serviceTaskdept = new ServiceTaskdept(); |
| | |
| | | } |
| | | if (CollectionUtils.isNotEmpty(serviceTaskVO.getPatTaskRelevances())) { |
| | | for (PatTaskRelevance patTaskRelevance : serviceTaskVO.getPatTaskRelevances()) { |
| | | ServiceSubtask serviceSubtask = DtoConversionUtils.sourceToTarget(serviceTaskVO, ServiceSubtask.class); |
| | | // 科室/病区按「离院维度」归位(出院随访的科室病区就是离院科室/病区),见 toSubtaskFromTaskVO |
| | | ServiceSubtask serviceSubtask = toSubtaskFromTaskVO(serviceTaskVO); |
| | | serviceSubtask.setLibtemplateid(ObjectUtils.isEmpty(serviceTaskVO.getLibtemplateid()) ? null : Long.valueOf(serviceTaskVO.getLibtemplateid())); |
| | | serviceSubtask.setLibtemplatename(serviceTaskVO.getLibtemplatename()); |
| | | serviceSubtask.setTemplateid(serviceTask.getTemplateid()); |
| | |
| | | serviceSubtask.setAddr(patTaskRelevance.getAddr()); |
| | | serviceSubtask.setPatid(patTaskRelevance.getPatid()); |
| | | serviceSubtask.setOpenid(patTaskRelevance.getOpenid()); |
| | | serviceSubtask.setDeptcode(patTaskRelevance.getDeptcode()); |
| | | serviceSubtask.setLeavehospitaldistrictname(patTaskRelevance.getLeavehospitaldistrictname()); |
| | | serviceSubtask.setLeavehospitaldistrictcode(patTaskRelevance.getLeavehospitaldistrictcode()); |
| | | serviceSubtask.setDeptname(patTaskRelevance.getDeptName()); |
| | | // 离院维度:患者自带的出院科室/病区优先,没带时回退到任务配置(否则会把 |
| | | // toSubtaskFromTaskVO 按任务配置兜好的值抹成 null,出院随访就成「离院科室/病区为空」) |
| | | serviceSubtask.setLeaveldeptcode(firstNonBlank(patTaskRelevance.getDeptcode(), |
| | | firstValue(serviceTaskVO.getDeptcode()))); |
| | | serviceSubtask.setLeaveldeptname(firstNonBlank(patTaskRelevance.getDeptName(), |
| | | firstValue(serviceTaskVO.getDeptname()))); |
| | | serviceSubtask.setLeavehospitaldistrictcode(firstNonBlank(patTaskRelevance.getLeavehospitaldistrictcode(), |
| | | firstValue(serviceTaskVO.getLeavehospitaldistrictcode()))); |
| | | serviceSubtask.setLeavehospitaldistrictname(firstNonBlank(patTaskRelevance.getLeavehospitaldistrictname(), |
| | | firstValue(serviceTaskVO.getLeavehospitaldistrictname()))); |
| | | serviceSubtask.setType(serviceTaskVO.getType()); |
| | | serviceSubtask.setCreateTime(DateUtils.getNowDate()); |
| | | serviceSubtask.setDrname(patTaskRelevance.getDrname()); |
| | |
| | | if (serviceSubtaskCountReq.getDrcode() != null && !serviceSubtaskCountReq.getDrcode().isEmpty()) { |
| | | groupKey = drcode; |
| | | } else { |
| | | groupKey = "deptcode"; |
| | | // 2026-11 字段重命名后:deptcode 是在院科室(历史数据为空), |
| | | // 出院科室已改名为 leaveldeptcode,分组必须用后者, |
| | | // 否则 getSfStatisticsGroupKey 会返回 NULL,拼成 IN (NULL) 导致整表查空。 |
| | | groupKey = "leaveldeptcode"; |
| | | } |
| | | } else if (serviceSubtaskCountReq.getStatisticaltype() != null && serviceSubtaskCountReq.getStatisticaltype() == 3) { |
| | | // 病种统计:按任务ID分组,每个任务即一个病种随访任务 |
| | | groupKey = "taskid"; |
| | | } |
| | | List<ServiceSubtaskStatistic> serviceSubtaskStatistics = new ArrayList<>(); |
| | | // 根据条件进行分组 |
| | | Map<String, List<ServiceSubtask>> collect = new HashMap<>(); |
| | | serviceSubtaskCountReq.setGroupKey(groupKey); |
| | | List<String> groupKeyList = serviceSubtaskMapper.getSfStatisticsGroupKey(serviceSubtaskCountReq); |
| | | /* |
| | | * 分组键可能为 NULL(历史数据、或重命名后尚未回填的在院维度列), |
| | | * 直接拼进 SQL 会变成 `AND IFNULL(列,'Unknown') IN (NULL)`,恒不成立,整张统计表为空。 |
| | | * 这里统一成 'Unknown',与聚合 SQL 的 IFNULL(列,'Unknown') 口径对齐。 |
| | | */ |
| | | if (groupKeyList != null) { |
| | | groupKeyList = groupKeyList.stream() |
| | | .map(key -> key == null ? "Unknown" : key) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | } |
| | | serviceSubtaskCountReq.setGroupKeyList(groupKeyList); |
| | | List<ServiceSubtask> rawData = serviceSubtaskMapper.getSfStatistics(serviceSubtaskCountReq); |
| | | switch (groupKey) { |
| | |
| | | collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getManagementDoctorCode()).orElse("Unknown"))); |
| | | break; |
| | | case "deptcode": |
| | | collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDeptcode()).orElse("Unknown"))); |
| | | case "leaveldeptcode": |
| | | collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getLeaveldeptcode()).orElse("Unknown"))); |
| | | break; |
| | | case "leavehospitaldistrictcode": |
| | | collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getLeavehospitaldistrictcode()).orElse("Unknown"))); |
| | | break; |
| | | case "taskid": |
| | | collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getTaskid()).map(String::valueOf).orElse("Unknown"))); |
| | | break; |
| | | default: |
| | | collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDrcode()).orElse("Unknown"))); |
| | |
| | | continue; |
| | | } |
| | | ServiceSubtaskStatistic statistic = getStatistic(serviceSubtaskList, serviceSubtaskCountReq.getFollowUpCountStyle(), serviceSubtaskCountReq.getVisitCount()); |
| | | if ("taskid".equals(groupKey)) { |
| | | applyTaskGroupInfo(statistic, serviceSubtaskList); |
| | | } |
| | | if (ObjectUtils.isNotEmpty(jsRates)) { |
| | | Map<String, Object> jsRateMap = jsRates.get(collectKey); |
| | | BigDecimal jsRate = new BigDecimal("0"); |
| | |
| | | * @return |
| | | */ |
| | | public Map<String, Map<String, Object>> getSsRateForSfStatistics(ServiceSubtaskCountReq serviceSubtaskCountReq, Map<String, List<ServiceSubtask>> collect) { |
| | | if (ObjectUtils.isEmpty(serviceSubtaskCountReq) || ObjectUtils.isEmpty(collect)) { |
| | | return null; |
| | | } |
| | | return getSsRateForSfStatistics(serviceSubtaskCountReq, new ArrayList<>(collect.keySet())); |
| | | } |
| | | |
| | | /** |
| | | * 随访统计-及时率统计(分组键直接传值的重载) |
| | | * <p> |
| | | * 统计下推到 SQL 的 {@code getSfStatisticsScript} 不再把明细行捞回内存,拿不到 |
| | | * {@code Map<分组, List<明细>>},因此直接传分组键集合;查询语句与原来完全同一个。 |
| | | * |
| | | * @param groupKeyList 分组键集合(drcode / leaveldeptcode / leavehospitaldistrictcode 的实际取值) |
| | | */ |
| | | public Map<String, Map<String, Object>> getSsRateForSfStatistics(ServiceSubtaskCountReq serviceSubtaskCountReq, List<String> groupKeyList) { |
| | | if (ObjectUtils.isEmpty(serviceSubtaskCountReq)) { |
| | | return null; |
| | | } |
| | |
| | | if (StringUtils.isEmpty(groupKey)) { |
| | | return null; |
| | | } |
| | | List<String> groupKeyList = new ArrayList<>(collect.keySet()); |
| | | if (ObjectUtils.isEmpty(groupKeyList)) { |
| | | return null; |
| | | } |
| | |
| | | ssRate.setGroupKey(groupKey); |
| | | ssRate.setGroupKeyList(groupKeyList); |
| | | ssRate.setRateDay(serviceSubtaskCountReq.getRateDay()); |
| | | Map<String, Map<String, Object>> rateResult = serviceSubtaskMapper.selectTimelyRateBatch(ssRate); |
| | | if (rateResult == null || rateResult.isEmpty()) { |
| | | return rateResult; |
| | | } |
| | | // taskid 为数值类型,MyBatis 返回的 Map key 可能是 Long,这里统一转成 String 与分组 key 对齐 |
| | | Map<String, Map<String, Object>> jsRates = new HashMap<>(); |
| | | if (!collect.isEmpty()) { |
| | | jsRates = serviceSubtaskMapper.selectTimelyRateBatch(ssRate); |
| | | for (Map.Entry<String, Map<String, Object>> entry : rateResult.entrySet()) { |
| | | jsRates.put(String.valueOf(entry.getKey()), entry.getValue()); |
| | | } |
| | | return jsRates; |
| | | } |
| | | |
| | | /** |
| | | * 空值兜底:SUM(CASE ...) 理论上不会是 null,这里只做防御,避免拆箱 NPE。 |
| | | */ |
| | | private long nvlLong(Long value) { |
| | | return value == null ? 0L : value; |
| | | } |
| | | |
| | | /** |
| | | * 把数据库聚合值(BigDecimal / Long / Integer 等)转成 int。 |
| | | */ |
| | | private int toIntValue(Object value) { |
| | | return value instanceof Number ? ((Number) value).intValue() : 0; |
| | | } |
| | | |
| | | public List<ServiceSubtaskStatistic> getSfStatisticsExport(ServiceSubtaskCountReq serviceSubtaskCountReq) { |
| | |
| | | ServiceSubtaskStatistic totalRow = calculateTotalRow(sfStatistics); |
| | | totalRow.setLeavehospitaldistrictname("总计"); |
| | | totalRow.setDeptname("总计"); |
| | | if (serviceSubtaskCountReq.getStatisticaltype() != null && serviceSubtaskCountReq.getStatisticaltype() == 3) { |
| | | totalRow.setTaskName("总计"); |
| | | } |
| | | sfStatistics.add(totalRow); |
| | | } |
| | | |
| | |
| | | if (serviceSubtaskCountReq.getDrcode() != null && !serviceSubtaskCountReq.getDrcode().isEmpty()) { |
| | | collect = serviceSubtaskMapper.getSfStatistics(serviceSubtaskCountReq).stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDrname()).orElse("Unknown"))); |
| | | } else { |
| | | collect = serviceSubtaskMapper.getSfStatistics(serviceSubtaskCountReq).stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDeptname()).orElse("Unknown"))); |
| | | collect = serviceSubtaskMapper.getSfStatistics(serviceSubtaskCountReq).stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getLeaveldeptname()).orElse("Unknown"))); |
| | | } |
| | | } |
| | | List<ServiceSubtaskCount> joySubCount = serviceSubtaskMapper.getSfStatisticsJoy(serviceSubtaskCountReq); |
| | |
| | | if (CollectionUtils.isNotEmpty(serviceSubtaskCountReq.getLeavehospitaldistrictcodes())) { |
| | | ss.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode()); |
| | | } else if (CollectionUtils.isNotEmpty(serviceSubtaskCountReq.getDeptcodes())) { |
| | | ss.setDeptcode(serviceSubtaskList.get(0).getDeptcode()); |
| | | ss.setLeaveldeptcode(serviceSubtaskList.get(0).getLeaveldeptcode()); |
| | | } |
| | | } |
| | | |
| | |
| | | return serviceSubtaskStatistics; |
| | | } |
| | | |
| | | /** |
| | | * 病种统计(按 taskid 分组)时补齐任务ID、任务名称和疾病病种。 |
| | | * |
| | | * @param statistic 统计结果 |
| | | * @param serviceSubtaskList 当前任务分组下的子任务 |
| | | */ |
| | | private void applyTaskGroupInfo(ServiceSubtaskStatistic statistic, List<ServiceSubtask> serviceSubtaskList) { |
| | | if (statistic == null || CollectionUtils.isEmpty(serviceSubtaskList)) { |
| | | return; |
| | | } |
| | | ServiceSubtask first = serviceSubtaskList.get(0); |
| | | statistic.setTaskId(first.getTaskid()); |
| | | statistic.setTaskName(first.getTaskName()); |
| | | statistic.setDiagType(first.getDiagType()); |
| | | } |
| | | |
| | | private ServiceSubtaskStatistic getStatistic(List<ServiceSubtask> serviceSubtaskList, String followUpCountStyle, Integer visitCount) { |
| | | ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic(); |
| | | |
| | |
| | | } else { |
| | | //格式化为两位小数的百分比 |
| | | DecimalFormat percentFormat = new DecimalFormat("##.##%"); |
| | | serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname()); |
| | | serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode()); |
| | | serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname()); |
| | | serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode()); |
| | | serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname()); |
| | | serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode()); |
| | | serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode()); |
| | | serviceSubtaskStatistic.setDrname(serviceSubtaskList.get(0).getDrname()); |
| | | //随访中心科室,用于统计再次随访推送到随访中心的人数 |
| | | String centerDept = sysConfigService.selectConfigByKey("sys.smartor.center.dept", serviceSubtaskList.get(0).getOrgid()); |
| | | //出院人次 |
| | | serviceSubtaskStatistic.setDischargeCount(serviceSubtaskList.size()); |
| | | for (ServiceSubtask serviceSubtask : serviceSubtaskList) { |
| | |
| | | if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) { |
| | | serviceSubtaskStatistic.setFollowUpNeeded(serviceSubtaskStatistic.getFollowUpNeeded() + 1L); |
| | | } |
| | | //及时随访人次:已完成且未超时 |
| | | if (isTimelyFollowUp(serviceSubtask)) { |
| | | serviceSubtaskStatistic.setTimelyFollowUp(serviceSubtaskStatistic.getTimelyFollowUp() + 1L); |
| | | } |
| | | |
| | | //首次出院人次:visitCount = 1,作为再次随访推送率的分母 |
| | | if (serviceSubtask.getVisitCount() != null && serviceSubtask.getVisitCount() == 1) { |
| | | serviceSubtaskStatistic.setFirstDischargeCount(serviceSubtaskStatistic.getFirstDischargeCount() + 1L); |
| | | } |
| | | //再次随访推送人数:首个再次随访(visitCount = 2)且执行科室为随访中心科室 |
| | | if (serviceSubtask.getVisitCount() != null && serviceSubtask.getVisitCount() == 2 |
| | | && StringUtils.isNotEmpty(centerDept) |
| | | && StringUtils.equals(StringUtils.trim(centerDept), StringUtils.trim(serviceSubtask.getVisitDeptCode()))) { |
| | | serviceSubtaskStatistic.setAgainFollowUpPushCount(serviceSubtaskStatistic.getAgainFollowUpPushCount() + 1L); |
| | | } |
| | | |
| | | //人工已拨通总量 |
| | | if (ObjectUtils.isNotEmpty(serviceSubtask.getIsManuallyCalled()) |
| | | && serviceSubtask.getIsManuallyCalled().equals(1L)) { |
| | | serviceSubtaskStatistic.setManuallyCalled(serviceSubtaskStatistic.getManuallyCalled() + 1L); |
| | | } |
| | | |
| | | //首次出院随访 |
| | | if (serviceSubtask.getVisitCount() != null && serviceSubtask.getVisitCount() == 1) { |
| | | applyFirstFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask); |
| | | //首次应随访 |
| | | if (serviceSubtask.getSendstate() != null && !serviceSubtask.getSendstate().equals(4L)) { |
| | | serviceSubtaskStatistic.setNeedFollowUp(serviceSubtaskStatistic.getNeedFollowUp() + 1L); |
| | |
| | | } |
| | | }//二次出院随访 |
| | | else if (serviceSubtask.getVisitCount() != null && serviceSubtask.getVisitCount() > 1) { |
| | | applyAgainFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask); |
| | | //再次应随访 |
| | | if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) { |
| | | serviceSubtaskStatistic.setNeedFollowUpAgain(serviceSubtaskStatistic.getNeedFollowUpAgain() + 1L); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 是否属于「及时随访」:随访已完成且完成时状态为正常完成(未超时)。 |
| | | * <p> |
| | | * finish_state:1-正常完成 2-延期完成 3-超时完成,只有 1 计入及时随访。 |
| | | * |
| | | * @param serviceSubtask 随访子任务 |
| | | * @return true-计入及时随访人次 |
| | | */ |
| | | private boolean isTimelyFollowUp(ServiceSubtask serviceSubtask) { |
| | | return serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate().equals(6L) |
| | | && serviceSubtask.getFinishState() != null && serviceSubtask.getFinishState().equals(1L); |
| | | } |
| | | |
| | | /** |
| | | * 市一统计口径:首次待随访、已发送及人工电话超时。 |
| | | */ |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 再次随访附加统计:待随访、已发送、人工电话超时及回院复诊复查(suggest=7)。 |
| | | */ |
| | | private void applyAgainFollowUpExtraStatistics(ServiceSubtaskStatistic statistic, ServiceSubtask serviceSubtask) { |
| | | if (serviceSubtask.getVisitCount() == null || serviceSubtask.getVisitCount() <= 1) { |
| | | return; |
| | | } |
| | | if (serviceSubtask.getSendstate() == null) { |
| | | return; |
| | | } |
| | | Long sendstate = serviceSubtask.getSendstate(); |
| | | // 回院复诊复查:再次随访中按处理意见 suggest=7 统计 |
| | | if (StringUtils.equals("7", StringUtils.trim(serviceSubtask.getSuggest()))) { |
| | | statistic.setSuggest7CountAgain(statistic.getSuggest7CountAgain() + 1L); |
| | | } |
| | | 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 |
| | |
| | | ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic(); |
| | | //格式化为两位小数的百分比 |
| | | DecimalFormat percentFormat = new DecimalFormat("##.##%"); |
| | | serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname()); |
| | | serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode()); |
| | | serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname()); |
| | | serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode()); |
| | | serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname()); |
| | | serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode()); |
| | | serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode()); |
| | |
| | | serviceSubtaskStatistic.setDischargeCount(serviceSubtaskList.size()); |
| | | |
| | | for (ServiceSubtask serviceSubtask : serviceSubtaskList) { |
| | | applyFirstFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask); |
| | | //无需随访人次 |
| | | if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 4) { |
| | | serviceSubtaskStatistic.setNonFollowUp(serviceSubtaskStatistic.getNonFollowUp() + 1L); |
| | |
| | | //应随访人次 |
| | | if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) { |
| | | serviceSubtaskStatistic.setFollowUpNeeded(serviceSubtaskStatistic.getFollowUpNeeded() + 1L); |
| | | } |
| | | //及时随访人次:已完成且未超时 |
| | | if (isTimelyFollowUp(serviceSubtask)) { |
| | | serviceSubtaskStatistic.setTimelyFollowUp(serviceSubtaskStatistic.getTimelyFollowUp() + 1L); |
| | | } |
| | | |
| | | //首次出院随访 |
| | |
| | | ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic(); |
| | | //格式化为两位小数的百分比 |
| | | DecimalFormat percentFormat = new DecimalFormat("##.##%"); |
| | | serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname()); |
| | | serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode()); |
| | | serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname()); |
| | | serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode()); |
| | | serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname()); |
| | | serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode()); |
| | | serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode()); |
| | |
| | | //出院人次 |
| | | serviceSubtaskStatistic.setDischargeCount(serviceSubtaskList.size()); |
| | | for (ServiceSubtask serviceSubtask : serviceSubtaskList) { |
| | | applyAgainFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask); |
| | | //无需随访人次 |
| | | if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 4) { |
| | | serviceSubtaskStatistic.setNonFollowUp(serviceSubtaskStatistic.getNonFollowUp() + 1L); |
| | |
| | | //应随访人次 |
| | | if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) { |
| | | serviceSubtaskStatistic.setFollowUpNeeded(serviceSubtaskStatistic.getFollowUpNeeded() + 1L); |
| | | } |
| | | //及时随访人次:已完成且未超时 |
| | | if (isTimelyFollowUp(serviceSubtask)) { |
| | | serviceSubtaskStatistic.setTimelyFollowUp(serviceSubtaskStatistic.getTimelyFollowUp() + 1L); |
| | | } |
| | | |
| | | //再次出院随访 |
| | |
| | | ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic(); |
| | | //格式化为两位小数的百分比 |
| | | DecimalFormat percentFormat = new DecimalFormat("##.##%"); |
| | | serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname()); |
| | | serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode()); |
| | | serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname()); |
| | | serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode()); |
| | | serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname()); |
| | | serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode()); |
| | | serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode()); |
| | |
| | | ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic(); |
| | | //格式化为两位小数的百分比 |
| | | DecimalFormat percentFormat = new DecimalFormat("##.##%"); |
| | | serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname()); |
| | | serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode()); |
| | | serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname()); |
| | | serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode()); |
| | | serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname()); |
| | | serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode()); |
| | | serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode()); |
| | | serviceSubtaskStatistic.setDrname(serviceSubtaskList.get(0).getDrname()); |
| | | serviceSubtaskStatistic.setJoyCount(0); |
| | | serviceSubtaskStatistic.setJoyAllCount(0); |
| | | |
| | | //出院人次 |
| | | serviceSubtaskStatistic.setDischargeCount(serviceSubtaskList.size()); |
| | | //获取随访统计 |
| | | if (subCounts == null) { |
| | | subCounts = new ArrayList<>(); |
| | |
| | | * 获取随访统计超链接 |
| | | * dischargeCountInfo |
| | | * followUpNeededAllInfo - sendstate != 4 (1,2,3,5,6,7) |
| | | * firstDischargeCountInfo - visitCount = 1 |
| | | * againFollowUpPushCountInfo - visitCount = 2 && visitDeptCode = sys.smartor.center.dept(随访中心科室) |
| | | * nonFollowUpInfo - sendstate = 4 |
| | | * needFollowUpInfo - visitCount = 1 && sendstate != 4 |
| | | * pendingFollowUpInfo - visitCount = 1 && sendstate = 2 |
| | | * followUpSuccessInfo - visitCount = 1 && sendstate = 6 |
| | | * followUpFailInfo - visitCount = 1 && (sendstate = 5 || sendstate = 7) |
| | | * timelyFollowUpInfo - sendstate = 6 && finish_state = 1(沿用请求的 visitCount 口径) |
| | | * manualInfo - visitCount = 1 && currentPreachForm = "1" |
| | | * voiceInfo - visitCount = 1 && currentPreachForm = "3" |
| | | * smsInfo - visitCount = 1 && currentPreachForm = "4" |
| | |
| | | * taskSituation4Info - taskSituation = 4 |
| | | * 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 |
| | | * suggest7CountAgainInfo - visitCount > 1 && suggest = 7 |
| | | * filterCountList - taskSituation = 6 && sendstate = 4 |
| | | * |
| | | */ |
| | |
| | | public List<ServiceSubtask> getSfStatisticsHyperlink(ServiceSubtaskCountReq serviceSubtaskCountReq) { |
| | | String hyperLinkInfoType = serviceSubtaskCountReq.getHyperLinkInfoType(); |
| | | if (StringUtils.isNotEmpty(hyperLinkInfoType)) { |
| | | applyHyperLinkInfoType(serviceSubtaskCountReq, hyperLinkInfoType); |
| | | if (!applyHyperLinkInfoType(serviceSubtaskCountReq, hyperLinkInfoType)) { |
| | | // 下钻依赖的配置缺失时返回空明细,避免退化成无过滤条件的查询 |
| | | return new ArrayList<>(); |
| | | } |
| | | } |
| | | |
| | | String followUpCountStyle = serviceSubtaskCountReq.getFollowUpCountStyle(); |
| | |
| | | return serviceSubtasks; |
| | | } |
| | | |
| | | private void applyHyperLinkInfoType(ServiceSubtaskCountReq serviceSubtaskCountReq, String hyperLinkInfoType) { |
| | | private boolean applyHyperLinkInfoType(ServiceSubtaskCountReq serviceSubtaskCountReq, String hyperLinkInfoType) { |
| | | //及时随访明细需要沿用请求的首次/再次口径 |
| | | Integer originVisitCount = serviceSubtaskCountReq.getVisitCount(); |
| | | serviceSubtaskCountReq.setVisitCount(null); |
| | | serviceSubtaskCountReq.setSendstates(null); |
| | | serviceSubtaskCountReq.setCurrentPreachform(null); |
| | | serviceSubtaskCountReq.setExcep(null); |
| | | serviceSubtaskCountReq.setTaskSituation(null); |
| | | serviceSubtaskCountReq.setFinishState(null); |
| | | serviceSubtaskCountReq.setSuggest(null); |
| | | |
| | | switch (hyperLinkInfoType) { |
| | | case "dischargeCountInfo": |
| | | break; |
| | | case "firstDischargeCountInfo": |
| | | //首次出院人次:visitCount = 1 |
| | | serviceSubtaskCountReq.setVisitCount(1); |
| | | break; |
| | | case "againFollowUpPushCountInfo": { |
| | | //再次随访推送人数:visitCount = 2 且执行科室为随访中心科室 |
| | | String centerDept = sysConfigService.selectConfigByKey("sys.smartor.center.dept", serviceSubtaskCountReq.getOrgid()); |
| | | if (StringUtils.isBlank(centerDept)) { |
| | | log.warn("获取再次随访推送明细失败,未配置随访中心科室参数 sys.smartor.center.dept, orgid={}", serviceSubtaskCountReq.getOrgid()); |
| | | return false; |
| | | } |
| | | serviceSubtaskCountReq.setVisitCount(2); |
| | | serviceSubtaskCountReq.setVisitDeptCode(centerDept.trim()); |
| | | serviceSubtaskCountReq.setVisitDeptName(null); |
| | | break; |
| | | } |
| | | case "timelyFollowUpInfo": |
| | | serviceSubtaskCountReq.setVisitCount(originVisitCount); |
| | | serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L))); |
| | | serviceSubtaskCountReq.setFinishState(1L); |
| | | break; |
| | | case "followUpNeededAllInfo": |
| | | serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 5L, 6L, 7L))); |
| | |
| | | serviceSubtaskCountReq.setVisitCount(1); |
| | | serviceSubtaskCountReq.setCurrentPreachform("4"); |
| | | break; |
| | | case "wechatInfo": |
| | | case "weChatInfo": |
| | | serviceSubtaskCountReq.setVisitCount(1); |
| | | serviceSubtaskCountReq.setCurrentPreachform("5"); |
| | | break; |
| | |
| | | serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L))); |
| | | serviceSubtaskCountReq.setCurrentPreachform("4"); |
| | | break; |
| | | case "wechatSuccessInfo": |
| | | case "weChatSuccessInfo": |
| | | serviceSubtaskCountReq.setVisitCount(1); |
| | | serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L))); |
| | | serviceSubtaskCountReq.setCurrentPreachform("5"); |
| | |
| | | serviceSubtaskCountReq.setVisitCount(2); |
| | | serviceSubtaskCountReq.setCurrentPreachform("4"); |
| | | break; |
| | | case "wechatAgainInfo": |
| | | case "weChatAgainInfo": |
| | | serviceSubtaskCountReq.setVisitCount(2); |
| | | serviceSubtaskCountReq.setCurrentPreachform("5"); |
| | | break; |
| | |
| | | serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L))); |
| | | serviceSubtaskCountReq.setCurrentPreachform("4"); |
| | | break; |
| | | case "wechatAgainSuccessInfo": |
| | | case "weChatAgainSuccessInfo": |
| | | serviceSubtaskCountReq.setVisitCount(2); |
| | | serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L))); |
| | | serviceSubtaskCountReq.setCurrentPreachform("5"); |
| | |
| | | case "abnormalAgainInfo": |
| | | serviceSubtaskCountReq.setVisitCount(2); |
| | | serviceSubtaskCountReq.setExcep("1"); |
| | | break; |
| | | case "suggest7CountAgainInfo": |
| | | // 再次随访回院复诊复查:visitCount > 1 且 suggest = 7 |
| | | serviceSubtaskCountReq.setVisitCount(2); |
| | | serviceSubtaskCountReq.setSuggest("7"); |
| | | break; |
| | | case "taskSituation1Info": |
| | | serviceSubtaskCountReq.setTaskSituation(1); |
| | |
| | | serviceSubtaskCountReq.setTaskSituation(6); |
| | | serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(4L))); |
| | | 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; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return |
| | | */ |
| | | @Override |
| | | @Cacheable(value = "getSfStatisticsScript", key = "T(org.springframework.util.DigestUtils).md5DigestAsHex(#serviceSubtaskCountReq.toString().getBytes())", unless = "#result == null or #result.isEmpty()") |
| | | // @Cacheable(value = "getSfStatisticsScript", key = "T(org.springframework.util.DigestUtils).md5DigestAsHex(#serviceSubtaskCountReq.toString().getBytes())", unless = "#result == null or #result.isEmpty()") |
| | | public List<ServiceSubtaskStatistic> getSfStatisticsScript(ServiceSubtaskCountReq serviceSubtaskCountReq) { |
| | | log.info("getSfStatisticsScript的入参为:{}", serviceSubtaskCountReq); |
| | | List<ServiceSubtaskStatistic> serviceSubtaskStatistics = new ArrayList<>(); |
| | | |
| | | // 根据条件进行分组 |
| | | Map<String, List<ServiceSubtask>> collect = new HashMap<>(); |
| | | // 根据条件决定分组维度:离院维度按 leaveldeptcode / leavehospitaldistrictcode(含历史 deptcodes 入参), |
| | | // 在院维度按 deptcode / hospitaldistrictcode |
| | | String groupKey = "drcode"; |
| | | boolean hasDrcode = serviceSubtaskCountReq.getDrcode() != null && !serviceSubtaskCountReq.getDrcode().isEmpty(); |
| | | if (serviceSubtaskCountReq.getLeavehospitaldistrictcodes() != null && !serviceSubtaskCountReq.getLeavehospitaldistrictcodes().isEmpty()) { |
| | | if (serviceSubtaskCountReq.getDrcode() != null && !serviceSubtaskCountReq.getDrcode().isEmpty()) { |
| | | groupKey = "drcode"; |
| | | } else { |
| | | groupKey = "leavehospitaldistrictcode"; |
| | | } |
| | | groupKey = hasDrcode ? "drcode" : "leavehospitaldistrictcode"; |
| | | } else if (serviceSubtaskCountReq.getLeaveldeptcodes() != null && !serviceSubtaskCountReq.getLeaveldeptcodes().isEmpty()) { |
| | | groupKey = hasDrcode ? "drcode" : "leaveldeptcode"; |
| | | } else if (serviceSubtaskCountReq.getHospitaldistrictcodes() != null && !serviceSubtaskCountReq.getHospitaldistrictcodes().isEmpty()) { |
| | | // 在院满意度:按在院病区分组(hospitaldistrictcode) |
| | | groupKey = hasDrcode ? "drcode" : "hospitaldistrictcode"; |
| | | } else if (serviceSubtaskCountReq.getHospitaldeptcodes() != null && !serviceSubtaskCountReq.getHospitaldeptcodes().isEmpty()) { |
| | | // 在院满意度:按在院科室分组(deptcode) |
| | | groupKey = hasDrcode ? "drcode" : "deptcode"; |
| | | } else if (serviceSubtaskCountReq.getDeptcodes() != null && !serviceSubtaskCountReq.getDeptcodes().isEmpty()) { |
| | | if (serviceSubtaskCountReq.getDrcode() != null && !serviceSubtaskCountReq.getDrcode().isEmpty()) { |
| | | groupKey = "drcode"; |
| | | } else { |
| | | groupKey = "deptcode"; |
| | | } |
| | | // 历史入参 deptcodes 表示离院科室 |
| | | groupKey = hasDrcode ? "drcode" : "leaveldeptcode"; |
| | | } else if (serviceSubtaskCountReq.getStatisticaltype() != null && serviceSubtaskCountReq.getStatisticaltype() == 1) { |
| | | // 历史入参(市一等老前端只传 statisticaltype):1 = 病区统计 |
| | | groupKey = hasDrcode ? "drcode" : "leavehospitaldistrictcode"; |
| | | } else if (serviceSubtaskCountReq.getStatisticaltype() != null && serviceSubtaskCountReq.getStatisticaltype() == 2) { |
| | | // 历史入参:2 = 科室统计(原 deptcode 即现在的离院科室 leaveldeptcode) |
| | | groupKey = hasDrcode ? "drcode" : "leaveldeptcode"; |
| | | } else if (serviceSubtaskCountReq.getStatisticaltype() != null && serviceSubtaskCountReq.getStatisticaltype() == 3) { |
| | | // 病种统计:按任务ID分组,每个任务即一个病种随访任务 |
| | | groupKey = "taskid"; |
| | | } |
| | | serviceSubtaskCountReq.setGroupKey(groupKey); |
| | | // 分组列与过滤列保持一致:离院科室 leaveldeptcode、在院科室 deptcode |
| | | serviceSubtaskCountReq.setGroupColumn(groupKey); |
| | | /* |
| | | * 满意度「病区统计」(离院维度):出院病区为空则回退到在院病区。 |
| | | * 满意度子任务(service_type 6/14/15/16)的 leavehospitaldistrictcode 约三成为空, |
| | | * 之前为了“查得到数据”把病区条件写成 `xxx IS NULL OR xxx IN (...)`, |
| | | * 于是选了 A 病区也会把空病区的全量数据带进来,再统一归到 'Unknown' 组, |
| | | * 该组的病区名又是 MAX(hospitaldistrictname) 随便挑的 —— 表现就是 |
| | | * “选了血液内科病区,却查出一行显示骨科中心三病区”。 |
| | | * 用“有效病区”列过滤 + 分组后:筛选真正生效,空病区的记录也回到它真实的在院病区。 |
| | | */ |
| | | if ("leavehospitaldistrictcode".equals(groupKey)) { |
| | | String districtColumn = "IFNULL(leavehospitaldistrictcode, hospitaldistrictcode)"; |
| | | serviceSubtaskCountReq.setGroupColumn(districtColumn); |
| | | serviceSubtaskCountReq.setDistrictColumn(districtColumn); |
| | | } |
| | | //分组键集合:这一步保持不变(分页参数照旧作用在该查询上),保证“参与统计的分组”与改造前一致 |
| | | List<String> groupKeyList = serviceSubtaskMapper.getSfStatisticsGroupKey(serviceSubtaskCountReq); |
| | | /* |
| | | * 分组列可能为 NULL:满意度子任务(service_type 6/14/15/16)的 leavehospitaldistrictcode |
| | | * 大量为 NULL,getSfStatisticsGroupKey 会把 NULL 原样带回来, |
| | | * 拼进 SQL 就变成 `AND xxx IN (null)`(恒不成立),整张统计表直接空。 |
| | | * 这里统一成 'Unknown',与聚合 SQL 的 GROUP BY IFNULL(列,'Unknown') 对齐。 |
| | | */ |
| | | if (groupKeyList != null) { |
| | | groupKeyList = groupKeyList.stream() |
| | | .map(key -> key == null ? "Unknown" : key) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | } |
| | | serviceSubtaskCountReq.setGroupKeyList(groupKeyList); |
| | | List<ServiceSubtask> rawData = serviceSubtaskMapper.getSfStatistics(serviceSubtaskCountReq); |
| | | switch (groupKey) { |
| | | case "deptcode": |
| | | collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDeptcode()).orElse("Unknown"))); |
| | | break; |
| | | case "drcode": |
| | | collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDrcode()).orElse("Unknown"))); |
| | | break; |
| | | case "leavehospitaldistrictcode": |
| | | collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getLeavehospitaldistrictcode()).orElse("Unknown"))); |
| | | break; |
| | | default: |
| | | collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDrcode()).orElse("Unknown"))); |
| | | break; |
| | | |
| | | /* |
| | | * 统计下推到数据库:一次 GROUP BY 只返回“分组数”行。 |
| | | * 改造前是 serviceSubtaskMapper.getSfStatistics() 把命中的全部明细行(39 个字段, |
| | | * 含 continue_content 等大字段)捞回 JVM 后逐行累加,几千行时传输和遍历都很慢。 |
| | | * 这里的聚合口径与原 getStatisticCategory 逐条对齐(见 Mapper 中 getSfStatisticsScriptSum 的注释)。 |
| | | */ |
| | | List<ServiceSubtaskStatistic> sumStatistics = serviceSubtaskMapper.getSfStatisticsScriptSum(serviceSubtaskCountReq); |
| | | if (CollectionUtils.isEmpty(sumStatistics)) { |
| | | return serviceSubtaskStatistics; |
| | | } |
| | | //获取及时率统计合集 |
| | | Map<String, Map<String, Object>> jsRates = getSsRateForSfStatistics(serviceSubtaskCountReq, collect); |
| | | //一次性查询所有分组的题目统计,避免每个分组一次SQL(N+1) |
| | | List<Long> allSubTaskIds = rawData.stream().map(ServiceSubtask::getId).filter(Objects::nonNull).distinct().collect(Collectors.toList()); |
| | | List<ServiceSubtaskCount> subCounts = new ArrayList<>(); |
| | | if (CollectionUtils.isNotEmpty(allSubTaskIds)) { |
| | | serviceSubtaskCountReq.setSubTaskIds(allSubTaskIds); |
| | | subCounts = serviceSubtaskMapper.getSfStatisticsCategory(serviceSubtaskCountReq); |
| | | } |
| | | for (String collectKey : collect.keySet()) { |
| | | List<ServiceSubtask> serviceSubtaskList = collect.get(collectKey); |
| | | if (CollectionUtils.isEmpty(serviceSubtaskList)) { |
| | | continue; |
| | | } |
| | | ServiceSubtaskStatistic statistic = getStatisticCategory(serviceSubtaskList, serviceSubtaskCountReq, subCounts); |
| | | if (ObjectUtils.isNotEmpty(jsRates)) { |
| | | Map<String, Object> jsRateMap = jsRates.get(collectKey); |
| | | BigDecimal jsRate = new BigDecimal("0"); |
| | | if (ObjectUtils.isNotEmpty(jsRateMap) && ObjectUtils.isNotEmpty(jsRateMap.get("rate"))) { |
| | | jsRate = new BigDecimal(jsRateMap.get("rate").toString()); |
| | | |
| | | /* |
| | | * 病种统计(按 taskid 分组)时补齐任务ID、任务名称和疾病病种。 |
| | | * 聚合 SQL 已返回 taskid/task_name,这里再补 diag_type。 |
| | | */ |
| | | if ("taskid".equals(groupKey)) { |
| | | Map<Long, String> diagTypeCache = new HashMap<>(); |
| | | for (ServiceSubtaskStatistic statistic : sumStatistics) { |
| | | Long taskId = statistic.getTaskId(); |
| | | if (taskId == null && StringUtils.isNotEmpty(statistic.getGroupCode())) { |
| | | try { |
| | | taskId = Long.valueOf(statistic.getGroupCode()); |
| | | } catch (NumberFormatException ignore) { |
| | | // 历史数据可能不是数值 taskid,保持原分组键即可 |
| | | } |
| | | } |
| | | Double jsRateDouble = (jsRate.setScale(4, RoundingMode.HALF_UP)).doubleValue(); |
| | | statistic.setRate(jsRateDouble); |
| | | statistic.setTaskId(taskId); |
| | | if (StringUtils.isEmpty(statistic.getTaskName())) { |
| | | statistic.setTaskName(statistic.getGroupName()); |
| | | } |
| | | if (taskId != null) { |
| | | if (!diagTypeCache.containsKey(taskId)) { |
| | | ServiceTask task = serviceTaskService.selectServiceTaskByTaskid(taskId); |
| | | diagTypeCache.put(taskId, task == null ? null : task.getDiagType()); |
| | | } |
| | | statistic.setDiagType(diagTypeCache.get(taskId)); |
| | | } |
| | | } |
| | | if (ObjectUtils.isNotEmpty(statistic)) { |
| | | serviceSubtaskStatistics.add(statistic); |
| | | } |
| | | |
| | | /* |
| | | * 满意度题目量:也改成按分组在库里聚合。 |
| | | * 改造前是先把本次命中的全部子任务 id 灌进 getSfStatisticsCategory 的 IN 列表查一遍, |
| | | * 再在内存里对每个子任务做一次 subCounts.stream().filter(...)(N*M 次比较)。 |
| | | */ |
| | | Map<String, Integer> joyCountMap = new HashMap<>(); |
| | | Map<String, Integer> joyAllCountMap = new HashMap<>(); |
| | | if (StringUtils.isNotEmpty(serviceSubtaskCountReq.getConfigValue())) { |
| | | List<Map<String, Object>> joySums = serviceSubtaskMapper.getSfStatisticsScriptJoySum(serviceSubtaskCountReq); |
| | | if (CollectionUtils.isNotEmpty(joySums)) { |
| | | for (Map<String, Object> joySum : joySums) { |
| | | String joyGroupCode = joySum.get("groupCode") == null ? "Unknown" : joySum.get("groupCode").toString(); |
| | | joyCountMap.put(joyGroupCode, toIntValue(joySum.get("joyCount"))); |
| | | joyAllCountMap.put(joyGroupCode, toIntValue(joySum.get("joyAllCount"))); |
| | | } |
| | | } |
| | | } |
| | | |
| | | //随访率:口径与原 getStatisticCategory 一致(首次率的判断沿用原来的 needFollowUp、再次率沿用原来的分母) |
| | | DecimalFormat percentFormat = new DecimalFormat("##.##%"); |
| | | List<String> statisticsGroupKeys = new ArrayList<>(); |
| | | for (ServiceSubtaskStatistic statistic : sumStatistics) { |
| | | String statisticGroupCode = statistic.getGroupCode() == null ? "Unknown" : statistic.getGroupCode(); |
| | | statistic.setJoyCount(joyCountMap.getOrDefault(statisticGroupCode, 0)); |
| | | statistic.setJoyAllCount(joyAllCountMap.getOrDefault(statisticGroupCode, 0)); |
| | | //首次随访率:只有该分组存在首次随访记录时才会被赋值(不存在则是 null,与改造前一致) |
| | | if (statistic.getFirstVisitCount() != null && statistic.getFirstVisitCount() > 0) { |
| | | if (statistic.getNeedFollowUp() != null && statistic.getNeedFollowUp() > 0) { |
| | | double rate = (double) (nvlLong(statistic.getFollowUpSuccess()) + nvlLong(statistic.getFollowUpFail())) / statistic.getNeedFollowUp(); |
| | | statistic.setFollowUpRate(percentFormat.format(rate)); |
| | | } else { |
| | | statistic.setFollowUpRate("0.00%"); |
| | | } |
| | | } |
| | | //再次随访率:判断条件与原实现一致(原来判断的是 needFollowUp,这里照搬以免口径变化) |
| | | if (statistic.getAgainVisitCount() != null && statistic.getAgainVisitCount() > 0) { |
| | | if (statistic.getNeedFollowUp() != null && statistic.getNeedFollowUp() > 0) { |
| | | double rateAgain = (double) (nvlLong(statistic.getFollowUpSuccessAgain()) + nvlLong(statistic.getFollowUpFailAgain())) / nvlLong(statistic.getNeedFollowUpAgain()); |
| | | statistic.setFollowUpRateAgain(percentFormat.format(rateAgain)); |
| | | } else { |
| | | statistic.setFollowUpRateAgain("0.00%"); |
| | | } |
| | | } |
| | | statisticsGroupKeys.add(statisticGroupCode); |
| | | serviceSubtaskStatistics.add(statistic); |
| | | } |
| | | |
| | | /* |
| | | * 病区统计:这一行的病区就是分组键本身,展示名取该组自己的病区名。 |
| | | * 聚合 SQL 里原来的 MAX(hospitaldistrictname) 在“一个组混了多个病区”时会挑出 |
| | | * 一个毫不相干的病区名(选血液内科病区显示成骨科中心三病区就是这么来的)。 |
| | | * 这里把分组键回填到在院/离院两套病区字段,保证前端不论读哪一列都是同一个病区, |
| | | * 也让“查看详情”带上来的病区编码能查回同一批数据。 |
| | | */ |
| | | if (StringUtils.isNotEmpty(serviceSubtaskCountReq.getDistrictColumn())) { |
| | | for (ServiceSubtaskStatistic statistic : serviceSubtaskStatistics) { |
| | | String districtCode = statistic.getGroupCode() == null ? "Unknown" : statistic.getGroupCode(); |
| | | String districtName = StringUtils.isNotEmpty(statistic.getGroupName()) ? statistic.getGroupName() : districtCode; |
| | | statistic.setLeavehospitaldistrictcode(districtCode); |
| | | statistic.setLeavehospitaldistrictname(districtName); |
| | | statistic.setHospitaldistrictcode(districtCode); |
| | | statistic.setHospitaldistrictname(districtName); |
| | | } |
| | | } |
| | | |
| | | //获取及时率统计合集(一次批量查询,不按分组循环查) |
| | | Map<String, Map<String, Object>> jsRates = getSsRateForSfStatistics(serviceSubtaskCountReq, statisticsGroupKeys); |
| | | for (ServiceSubtaskStatistic statistic : serviceSubtaskStatistics) { |
| | | String statisticGroupCode = statistic.getGroupCode() == null ? "Unknown" : statistic.getGroupCode(); |
| | | Map<String, Object> jsRateMap = jsRates == null ? null : jsRates.get(statisticGroupCode); |
| | | BigDecimal jsRate = new BigDecimal("0"); |
| | | if (ObjectUtils.isNotEmpty(jsRateMap) && ObjectUtils.isNotEmpty(jsRateMap.get("rate"))) { |
| | | jsRate = new BigDecimal(jsRateMap.get("rate").toString()); |
| | | } |
| | | statistic.setRate((jsRate.setScale(4, RoundingMode.HALF_UP)).doubleValue()); |
| | | } |
| | | |
| | | //统计随访题目分类 |
| | |
| | | sum = new BigDecimal(serviceSubtaskStatistic.getJoyAllCount()); |
| | | } |
| | | if (sum.intValue() != 0) { |
| | | finishedPercentage = finishedSum.divide(sum, 2, RoundingMode.HALF_UP).doubleValue(); |
| | | // 原来只保留 2 位小数(23634/26106 会被截成 0.91),前端再 ×100 |
| | | // 就显示成 91.00%,与实际的 90.53% 差了半个百分点。这里保留 4 位。 |
| | | finishedPercentage = finishedSum.divide(sum, 4, RoundingMode.HALF_UP).doubleValue(); |
| | | } |
| | | serviceSubtaskStatistic.setJoyAllCount(sum.intValue()); |
| | | serviceSubtaskStatistic.setJoyCount(finishedSum.intValue()); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, ServiceSubtaskScriptCount> getSfStatisticsScriptDetails(ServiceSubtaskCountReq serviceSubtaskCountReq) { |
| | | Map<String, ServiceSubtaskScriptCount> result = new HashMap<>(); |
| | | public Map<String, Object> getSfStatisticsScriptDetails(ServiceSubtaskCountReq serviceSubtaskCountReq) { |
| | | List<ServiceSubtaskScriptCount> allCounts = new ArrayList<>(); |
| | | List<ServiceSubtaskScriptCount> allItemCounts = new ArrayList<>(); |
| | | String type = serviceSubtaskCountReq.getType(); |
| | |
| | | allItemCounts = serviceSubtaskMapper.getSfStatisticsScriptItemDetails(serviceSubtaskCountReq); |
| | | } |
| | | |
| | | for (ServiceSubtaskScriptCount count : allCounts) { |
| | | //返回结构是 Map,且不传 type 时会执行两条 SQL,PageHelper 没法对整体分页, |
| | | //所以在题目合并完成之后按题目(scriptid)在这里分页,保证 total 和每页数据都是题目维度的。 |
| | | allCounts.sort(Comparator |
| | | .comparing(ServiceSubtaskScriptCount::getSubtaskType, Comparator.nullsLast(String::compareTo)) |
| | | .thenComparing(ServiceSubtaskScriptCount::getScriptid, Comparator.nullsLast(Long::compareTo))); |
| | | |
| | | int pageNum = ObjectUtils.isNotEmpty(serviceSubtaskCountReq.getPageNum()) && serviceSubtaskCountReq.getPageNum() > 0 |
| | | ? serviceSubtaskCountReq.getPageNum() : 1; |
| | | int pageSize = ObjectUtils.isNotEmpty(serviceSubtaskCountReq.getPageSize()) && serviceSubtaskCountReq.getPageSize() > 0 |
| | | ? serviceSubtaskCountReq.getPageSize() : 10; |
| | | int total = allCounts.size(); |
| | | int fromIndex = (int) Math.min((long) (pageNum - 1) * pageSize, total); |
| | | int toIndex = Math.min(fromIndex + pageSize, total); |
| | | List<ServiceSubtaskScriptCount> pageCounts = allCounts.subList(fromIndex, toIndex); |
| | | |
| | | Map<String, ServiceSubtaskScriptCount> pageData = new LinkedHashMap<>(); |
| | | for (ServiceSubtaskScriptCount count : pageCounts) { |
| | | Map<String, ServiceSubtaskScriptCount> itemCountMap = new HashMap<>(); |
| | | //问卷加上空白选项 |
| | | if (ObjectUtils.isNotEmpty(count.getTemplateid()) && ObjectUtils.isNotEmpty(count.getSubtaskType()) && count.getSubtaskType().equals("2")) { |
| | |
| | | } |
| | | } |
| | | allItemCounts.forEach(itemCount -> { |
| | | if (ObjectUtils.isNotEmpty(count.getScriptid()) && ObjectUtils.isNotEmpty(itemCount.getScriptid())) { |
| | | if (count.getScriptid().equals(itemCount.getScriptid())) { |
| | | //放入统计信息 |
| | | itemCount.setScriptContent(count.getScriptContent()); |
| | | itemCount.setScriptType(count.getScriptType()); |
| | | itemCount.setSubtaskType(count.getSubtaskType()); |
| | | itemCount.setCompletedQuantity(count.getCompletedQuantity()); |
| | | itemCount.setAllQuantity(count.getAllQuantity()); |
| | | itemCount.setCompletedPercentage(count.getCompletedPercentage()); |
| | | itemCount.setTemplateid(count.getTemplateid()); |
| | | //计算选择比例 |
| | | int chosenQuantity = ObjectUtils.isNotEmpty(itemCount.getChosenQuantity()) ? itemCount.getChosenQuantity() : 0; |
| | | int completedQuantity = itemCount.getCompletedQuantity(); |
| | | if (ObjectUtils.isNotEmpty(completedQuantity) && completedQuantity != 0) { |
| | | BigDecimal chosenPercentage = (new BigDecimal(chosenQuantity)).divide(new BigDecimal(completedQuantity), 2, RoundingMode.HALF_UP); |
| | | itemCount.setChosenPercentage(chosenPercentage.toString()); |
| | | } |
| | | itemCountMap.put(itemCount.getOptionText(), itemCount); |
| | | //明细行已按“题目内容+任务类型”聚合并带出 script_content,不能用 scriptid 匹配: |
| | | //同一道题会被复制到多个任务模板,每个模板的 scriptid 都不同 |
| | | boolean sameQuestion = ObjectUtils.isNotEmpty(count.getScriptContent()) |
| | | && ObjectUtils.isNotEmpty(itemCount.getScriptContent()) |
| | | && count.getScriptContent().equals(itemCount.getScriptContent()) |
| | | && Objects.equals(count.getSubtaskType(), itemCount.getSubtaskType()); |
| | | if (sameQuestion) { |
| | | //放入统计信息 |
| | | itemCount.setScriptContent(count.getScriptContent()); |
| | | itemCount.setScriptType(count.getScriptType()); |
| | | itemCount.setSubtaskType(count.getSubtaskType()); |
| | | itemCount.setCompletedQuantity(count.getCompletedQuantity()); |
| | | itemCount.setAllQuantity(count.getAllQuantity()); |
| | | itemCount.setCompletedPercentage(count.getCompletedPercentage()); |
| | | itemCount.setTemplateid(count.getTemplateid()); |
| | | //计算选择比例 |
| | | int chosenQuantity = ObjectUtils.isNotEmpty(itemCount.getChosenQuantity()) ? itemCount.getChosenQuantity() : 0; |
| | | int completedQuantity = itemCount.getCompletedQuantity(); |
| | | if (ObjectUtils.isNotEmpty(completedQuantity) && completedQuantity != 0) { |
| | | BigDecimal chosenPercentage = (new BigDecimal(chosenQuantity)).divide(new BigDecimal(completedQuantity), 2, RoundingMode.HALF_UP); |
| | | itemCount.setChosenPercentage(chosenPercentage.toString()); |
| | | } |
| | | itemCountMap.put(itemCount.getOptionText(), itemCount); |
| | | } |
| | | }); |
| | | List<ServiceSubtaskScriptCount> itemCounts = new ArrayList<>(itemCountMap.values()); |
| | | count.setDetails(itemCounts); |
| | | result.put(count.getScriptid().toString(), count); |
| | | pageData.put(count.getScriptid().toString(), count); |
| | | } |
| | | Map<String, Object> result = new HashMap<>(); |
| | | // total 是题目总数(已按题目合并),rows/data 是当前页数据 |
| | | result.put("total", total); |
| | | result.put("pageNum", pageNum); |
| | | result.put("pageSize", pageSize); |
| | | result.put("rows", new ArrayList<>(pageCounts)); |
| | | result.put("data", pageData); |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 问题选项数字下钻:返回选择了指定选项的 service_subtask 列表。 |
| | | * <p> |
| | | * 统计明细中的选项数量来自 service_subtask_detail 的 |
| | | * IFNULL(matchedtext, asrtext),因此下钻必须使用同一题目的相同选项进行匹配。 |
| | | * |
| | | * @param serviceSubtaskCountReq 下钻条件 |
| | | * @return service_subtask 列表 |
| | | */ |
| | | @Override |
| | | public List<ServiceSubtask> getSfStatisticsScriptDetailsHyperlink(ServiceSubtaskCountReq serviceSubtaskCountReq) { |
| | | log.info("getSfStatisticsScriptDetailsHyperlink的入参为:{}", serviceSubtaskCountReq); |
| | | if (serviceSubtaskCountReq == null) { |
| | | return new ArrayList<>(); |
| | | } |
| | | // 兼容前端直接回传统计明细行中的 subtaskType |
| | | if (StringUtils.isBlank(serviceSubtaskCountReq.getType()) |
| | | && StringUtils.isNotBlank(serviceSubtaskCountReq.getSubtaskType())) { |
| | | serviceSubtaskCountReq.setType(serviceSubtaskCountReq.getSubtaskType()); |
| | | } |
| | | resolveScriptContentAndType(serviceSubtaskCountReq); |
| | | if (StringUtils.isBlank(serviceSubtaskCountReq.getScriptContent()) |
| | | && serviceSubtaskCountReq.getScriptid() == null) { |
| | | log.warn("getSfStatisticsScriptDetailsHyperlink-题目scriptid和scriptContent均为空,返回空列表"); |
| | | return new ArrayList<>(); |
| | | } |
| | | if (serviceSubtaskCountReq.getOptionText() == null) { |
| | | log.warn("getSfStatisticsScriptDetailsHyperlink-选项optionText为空,返回空列表"); |
| | | return new ArrayList<>(); |
| | | } |
| | | return serviceSubtaskMapper.getSfStatisticsScriptDetailsHyperlink(serviceSubtaskCountReq); |
| | | } |
| | | |
| | | /** |
| | | * 根据 scriptid 补齐题目内容和任务类型。 |
| | | * <p> |
| | | * 统计明细按“题目内容 + 任务类型”合并,同一道题复制到多个任务模板时 scriptid 可能不同, |
| | | * 因此优先按题目内容下钻;只有查不到题目内容时才退化为 scriptid 精确匹配。 |
| | | */ |
| | | private void resolveScriptContentAndType(ServiceSubtaskCountReq serviceSubtaskCountReq) { |
| | | if (serviceSubtaskCountReq.getScriptid() == null) { |
| | | return; |
| | | } |
| | | // 已有题目内容和任务类型时无需再查模板问题表 |
| | | if (StringUtils.isNotBlank(serviceSubtaskCountReq.getScriptContent()) |
| | | && StringUtils.isNotBlank(serviceSubtaskCountReq.getType())) { |
| | | return; |
| | | } |
| | | |
| | | String type = serviceSubtaskCountReq.getType(); |
| | | if (StringUtils.isBlank(type) || "1".equals(type)) { |
| | | IvrTaskTemplateScript ivrTaskTemplateScript = |
| | | iIvrTaskTemplateScriptService.selectIvrTaskTemplateScriptByID(serviceSubtaskCountReq.getScriptid()); |
| | | if (ObjectUtils.isNotEmpty(ivrTaskTemplateScript)) { |
| | | if (StringUtils.isBlank(serviceSubtaskCountReq.getType())) { |
| | | serviceSubtaskCountReq.setType("1"); |
| | | } |
| | | if (StringUtils.isBlank(serviceSubtaskCountReq.getScriptContent())) { |
| | | serviceSubtaskCountReq.setScriptContent(ivrTaskTemplateScript.getScriptContent()); |
| | | } |
| | | return; |
| | | } |
| | | // 明确指定了语音类型时不再尝试问卷表 |
| | | if (StringUtils.isNotBlank(type)) { |
| | | return; |
| | | } |
| | | } |
| | | |
| | | if (StringUtils.isBlank(serviceSubtaskCountReq.getType()) || "2".equals(type)) { |
| | | SvyTaskTemplateScript svyTaskTemplateScript = |
| | | svyTaskTemplateScriptService.selectSvyTaskTemplateScriptByID(serviceSubtaskCountReq.getScriptid()); |
| | | if (ObjectUtils.isNotEmpty(svyTaskTemplateScript)) { |
| | | if (StringUtils.isBlank(serviceSubtaskCountReq.getType())) { |
| | | serviceSubtaskCountReq.setType("2"); |
| | | } |
| | | if (StringUtils.isBlank(serviceSubtaskCountReq.getScriptContent())) { |
| | | serviceSubtaskCountReq.setScriptContent(svyTaskTemplateScript.getScriptContent()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | // 初始化数值字段 |
| | | total.setDischargeCount(0); |
| | | total.setFirstDischargeCount(0L); |
| | | total.setAgainFollowUpPushCount(0L); |
| | | total.setNonFollowUp(0L); |
| | | total.setFilterCount(0L); |
| | | total.setFollowUpNeeded(0L); |
| | |
| | | total.setPendingFollowUp(0L); |
| | | total.setFollowUpSuccess(0L); |
| | | total.setFollowUpFail(0L); |
| | | total.setTimelyFollowUp(0L); |
| | | total.setManual(0L); |
| | | total.setSms(0L); |
| | | total.setWeChat(0L); |
| | | 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.setSuggest7CountAgain(0L); |
| | | total.setManualAgain(0L); |
| | | total.setSmsAgain(0L); |
| | | total.setWeChatAgain(0L); |
| | |
| | | if (stat.getDischargeCount() != null) { |
| | | total.setDischargeCount(total.getDischargeCount() + stat.getDischargeCount()); |
| | | } |
| | | total.setFirstDischargeCount(total.getFirstDischargeCount() + (stat.getFirstDischargeCount() != null ? stat.getFirstDischargeCount() : 0L)); |
| | | total.setAgainFollowUpPushCount(total.getAgainFollowUpPushCount() + (stat.getAgainFollowUpPushCount() != null ? stat.getAgainFollowUpPushCount() : 0L)); |
| | | total.setNonFollowUp(total.getNonFollowUp() + (stat.getNonFollowUp() != null ? stat.getNonFollowUp() : 0L)); |
| | | total.setFilterCount(total.getFilterCount() + (stat.getFilterCount() != null ? stat.getFilterCount() : 0L)); |
| | | total.setFollowUpNeeded(total.getFollowUpNeeded() + (stat.getFollowUpNeeded() != null ? stat.getFollowUpNeeded() : 0L)); |
| | |
| | | total.setPendingFollowUp(total.getPendingFollowUp() + (stat.getPendingFollowUp() != null ? stat.getPendingFollowUp() : 0L)); |
| | | total.setFollowUpSuccess(total.getFollowUpSuccess() + (stat.getFollowUpSuccess() != null ? stat.getFollowUpSuccess() : 0L)); |
| | | total.setFollowUpFail(total.getFollowUpFail() + (stat.getFollowUpFail() != null ? stat.getFollowUpFail() : 0L)); |
| | | total.setTimelyFollowUp(total.getTimelyFollowUp() + (stat.getTimelyFollowUp() != null ? stat.getTimelyFollowUp() : 0L)); |
| | | total.setManual(total.getManual() + (stat.getManual() != null ? stat.getManual() : 0L)); |
| | | total.setSms(total.getSms() + (stat.getSms() != null ? stat.getSms() : 0L)); |
| | | total.setWeChat(total.getWeChat() + (stat.getWeChat() != null ? stat.getWeChat() : 0L)); |
| | | 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.setSuggest7CountAgain(total.getSuggest7CountAgain() + (stat.getSuggest7CountAgain() != null ? stat.getSuggest7CountAgain() : 0L)); |
| | | total.setManualAgain(total.getManualAgain() + (stat.getManualAgain() != null ? stat.getManualAgain() : 0L)); |
| | | total.setSmsAgain(total.getSmsAgain() + (stat.getSmsAgain() != null ? stat.getSmsAgain() : 0L)); |
| | | total.setWeChatAgain(total.getWeChatAgain() + (stat.getWeChatAgain() != null ? stat.getWeChatAgain() : 0L)); |
| | |
| | | diagname.setPhone(subtask.getPhone()); |
| | | diagname.setNurseName(subtask.getNurseName()); |
| | | diagname.setDrname(subtask.getDrname()); |
| | | diagname.setDeptname(subtask.getDeptname()); |
| | | diagname.setDeptname(subtask.getLeaveldeptname()); |
| | | diagname.setLeavehospitaldistrictname(subtask.getLeavehospitaldistrictname()); |
| | | diagname.setTemplatename(subtask.getTemplatename()); |
| | | diagname.setPreachform(subtask.getPreachform()); |
| | |
| | | |
| | | public Map<String, Object> getCurrentUserServiceSubtaskCount(ServiceSubtaskEntity entity) { |
| | | return serviceSubtaskMapper.getCurrentUserServiceSubtaskCount(entity); |
| | | } |
| | | |
| | | /** |
| | | * 当前登录人待处理随访任务,按出院年月分组统计(出院随访、门诊随访分开计数) |
| | | * <p> |
| | | * 只返回有数据的月份,前端按「当月 + 前 3 个月」补齐,没有数据显示 0 个。 |
| | | */ |
| | | @Override |
| | | public Map<String, Object> getPendingManualTaskMonthStats(ServiceSubtaskEntity entity) { |
| | | List<Map<String, Object>> rows = serviceSubtaskMapper.countPendingManualTaskByMonth(entity); |
| | | List<Map<String, Object>> months = new ArrayList<>(); |
| | | long total = 0L; |
| | | if (rows != null) { |
| | | for (Map<String, Object> row : rows) { |
| | | if (row == null || row.get("month") == null) { |
| | | continue; |
| | | } |
| | | long dischargeCount = parsePendingCount(row.get("dischargeCnt")); |
| | | long outpatientCount = parsePendingCount(row.get("outpatientCnt")); |
| | | if (dischargeCount <= 0 && outpatientCount <= 0) { |
| | | continue; |
| | | } |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("month", row.get("month").toString()); |
| | | item.put("dischargeCount", dischargeCount); |
| | | item.put("outpatientCount", outpatientCount); |
| | | months.add(item); |
| | | total += dischargeCount + outpatientCount; |
| | | } |
| | | } |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("total", total); |
| | | result.put("months", months); |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 统计结果里的数量转 long,解析失败按 0 处理 |
| | | */ |
| | | private long parsePendingCount(Object value) { |
| | | if (value == null) { |
| | | return 0L; |
| | | } |
| | | try { |
| | | return Long.parseLong(value.toString()); |
| | | } catch (NumberFormatException e) { |
| | | log.warn("统计待处理随访任务失败,数量无法解析:{}", value); |
| | | return 0L; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | public void setFailSubTaskDefalutVal(Long failDay) { |
| | | //1.先去sys_config中,获取哪些科室或病区需要默认问卷默认值 |
| | | IConfigService configService = SpringUtils.getBean(IConfigService.class); |
| | | String deptConfigValue = taskSysConfigPort.selectConfigValue("hosp.default.dept"); |
| | | String wardConfigValue = taskSysConfigPort.selectConfigValue("hosp.default.ward"); |
| | | String deptConfigValue = sysConfigService.selectConfigByKey("hosp.default.dept"); |
| | | String wardConfigValue = sysConfigService.selectConfigByKey("hosp.default.ward"); |
| | | List<String> leaveWard = null; |
| | | List<String> leaveDept = null; |
| | | if (deptConfigValue != null && !deptConfigValue.isEmpty()) { |
| | |
| | | //3.如果查出来的集合不为空,则进行循环 |
| | | for (ServiceSubtask serviceSubtask : serviceSubtasks) { |
| | | Integer preachformSize = 0; |
| | | Boolean flag = false; |
| | | ServiceSubtaskPreachform serviceSubtaskPreachform = new ServiceSubtaskPreachform(); |
| | | serviceSubtaskPreachform.setSubid(serviceSubtask.getId()); |
| | | List<ServiceSubtaskPreachform> serviceSubtaskPreachforms = taskPreachformPort.selectServiceSubtaskPreachformList(serviceSubtaskPreachform); |
| | |
| | | preachformSize = serviceSubtaskPreachforms.size(); |
| | | if (serviceSubtask.getUpdateTime() != null && DateUtils.differentDaysByMillisecond(new Date(), serviceSubtask.getUpdateTime()) >= failDay) { |
| | | //3.1 判断当前serviceSubtask的updateTime是不是为空,如果不为空,则判断当前时间是不是与updateTime差距大于等于failDay |
| | | flag = saveServiceSubtaskDetail(serviceSubtask); |
| | | log.debug("【失败任务默认值】子任务 {} 达到失败天数(updateTime)", serviceSubtask.getId()); |
| | | } else if (serviceSubtask.getVisitTime() != null && DateUtils.differentDaysByMillisecond(new Date(), serviceSubtask.getVisitTime()) >= failDay + preachformSize) { |
| | | //3.2 如果当前serviceSubtask的updateTime为空,根据当前serviceSubtask的subid去查service_subtask_preachform得到size(如果size为空,则默认0),则根据当前时间是不是与当前serviceSubtask的visitTime差距大于等于failDay + size |
| | | flag = saveServiceSubtaskDetail(serviceSubtask); |
| | | log.debug("【失败任务默认值】子任务 {} 达到失败天数(visitTime+preachformSize)", serviceSubtask.getId()); |
| | | } else { |
| | | //3.3 如果2.1且2.2都为false,则continue |
| | | continue; |
| | | } |
| | | |
| | | //4.如果flag为true,则更新service_subtask表中的sendstate为6,preachform为1 |
| | | if (flag) { |
| | | serviceSubtask.setSendstate(6L); |
| | | serviceSubtask.setPreachform("1"); |
| | | serviceSubtaskMapper.updateServiceSubtask(serviceSubtask); |
| | | //4. ⚠️ 2026-10-21 加固:先"算完"再"落库",彻底去掉"先插入、失败再删除"的写法。 |
| | | // 旧逻辑是边插边判断,只要有一题没配默认值,就把这个 sub_id 下**所有**明细(含患者真实作答、 |
| | | // 甚至刚插进去的那批)统统置 del_flag='1' —— nhfy 2026-09-08 那 10 位患者的满意度作答 |
| | | // 就是这样被删掉、导致导出「问题详情占比」370 而「随访信息表单」380 的。 |
| | | // 现在:① 清单为空(=有题没配默认值)→ 整份跳过,不写也不删; |
| | | // ② 清单齐全 → 统一落库(存在则更新并复活,不存在则插入)。 |
| | | List<ServiceSubtaskDetail> defaultDetails = buildDefaultDetails(serviceSubtask); |
| | | if (CollectionUtils.isEmpty(defaultDetails)) { |
| | | log.info("【失败任务默认值】任务 {} 子任务 {} 有题目未配置默认值,整份跳过(不插入、不删除任何明细)", |
| | | serviceSubtask.getTaskid(), serviceSubtask.getId()); |
| | | continue; |
| | | } |
| | | persistDefaultDetails(defaultDetails); |
| | | |
| | | //5.全部落库成功后再把任务置为已完成 |
| | | serviceSubtask.setSendstate(6L); |
| | | serviceSubtask.setPreachform("1"); |
| | | serviceSubtaskMapper.updateServiceSubtask(serviceSubtask); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 按模板构造"默认值明细"清单(<b>只计算、不落库</b>) |
| | | * <p> |
| | | * 2026-10-21 新增:把原来的 {@code saveServiceSubtaskDetail}(边算边插、失败再删)拆成 |
| | | * 「build(本方法)」+「persist({@link #persistDefaultDetails})」两步, |
| | | * 保证"默认值不全"时**一个字节都不会写、更不会删**。 |
| | | * |
| | | * @param serviceSubtask 子任务 |
| | | * @return 明细清单;任一题没配默认值(或模板为空)时返回 {@code null},表示整份不处理 |
| | | */ |
| | | private List<ServiceSubtaskDetail> buildDefaultDetails(ServiceSubtask serviceSubtask) { |
| | | if (serviceSubtask.getType() == null) { |
| | | return null; |
| | | } |
| | | List<ServiceSubtaskDetail> details = new ArrayList<>(); |
| | | if (serviceSubtask.getType().equals("1")) { |
| | | //3.3.1 如果type是 1,则用当前serviceSubtask的templateid去ivr_task_temaplate_script中获取问题和选项 |
| | | IvrTaskTemplateScript ivrTaskTemplateScript = new IvrTaskTemplateScript(); |
| | | ivrTaskTemplateScript.setTemplateID(serviceSubtask.getTemplateid()); |
| | | List<IvrTaskTemplateScript> ivrTaskTemplateScripts = iIvrTaskTemplateScriptService.selectIvrTaskTemplateScriptList(ivrTaskTemplateScript); |
| | | if (CollectionUtils.isEmpty(ivrTaskTemplateScripts)) return null; |
| | | for (IvrTaskTemplateScript its : ivrTaskTemplateScripts) { |
| | | IvrTaskTemplateScriptVO ivrTaskTemplateScriptVO = DtoConversionUtils.sourceToTarget(its, IvrTaskTemplateScriptVO.class); |
| | | IvrTaskTemplateTargetoption ivrTaskTemplateTargetoption = new IvrTaskTemplateTargetoption(); |
| | | ivrTaskTemplateTargetoption.setScriptid(ivrTaskTemplateScriptVO.getId()); |
| | | List<IvrTaskTemplateTargetoption> ivrTaskTemplateTargetoptions = iIvrTaskTemplateTargetoptionService.selectIvrTaskTemplateTargetoptionList(ivrTaskTemplateTargetoption); |
| | | ivrTaskTemplateScriptVO.setIvrTaskScriptTargetoptionList(ivrTaskTemplateTargetoptions); |
| | | ServiceSubtaskDetail detail = buildSFDetail(ivrTaskTemplateScriptVO, serviceSubtask); |
| | | if (detail == null) return null; |
| | | details.add(detail); |
| | | } |
| | | } else if (serviceSubtask.getType().equals("2")) { |
| | | //3.3.2 如果type是 2,则用当前serviceSubtask的templateid去svy_task_temaplate_script中获取问题和选项 |
| | | SvyTaskTemplateScript svyTaskTemplateScript = new SvyTaskTemplateScript(); |
| | | svyTaskTemplateScript.setTemplateID(serviceSubtask.getTemplateid()); |
| | | List<SvyTaskTemplateScript> svyTaskTemplateScripts = svyTaskTemplateScriptService.selectSvyTaskTemplateScriptList(svyTaskTemplateScript); |
| | | if (CollectionUtils.isEmpty(svyTaskTemplateScripts)) return null; |
| | | for (SvyTaskTemplateScript taskTemplateScript : svyTaskTemplateScripts) { |
| | | SvyTaskTemplateScriptVO svyTaskTemplateScriptVO = DtoConversionUtils.sourceToTarget(taskTemplateScript, SvyTaskTemplateScriptVO.class); |
| | | SvyTaskTemplateScriptVO svyTaskTemplateScriptVO1 = svyTaskTemplateScriptService.selectInfoByCondition(svyTaskTemplateScriptVO); |
| | | ServiceSubtaskDetail detail = buildWJDetail(svyTaskTemplateScriptVO1, serviceSubtask); |
| | | if (detail == null) return null; |
| | | details.add(detail); |
| | | } |
| | | } else { |
| | | return null; |
| | | } |
| | | return details; |
| | | } |
| | | |
| | | /** |
| | | * 落库"默认值明细":存在则更新并复活,不存在则插入(<b>只增不删</b>) |
| | | */ |
| | | private void persistDefaultDetails(List<ServiceSubtaskDetail> details) { |
| | | for (ServiceSubtaskDetail detail : details) { |
| | | ServiceSubtaskDetailDTO exist = serviceSubtaskDetailMapper.isExistServiceSubtaskDetail(detail.getSubId(), detail.getScriptid()); |
| | | if (ObjectUtils.isNotEmpty(exist)) { |
| | | detail.setId(exist.getId()); |
| | | detail.setUpdateTime(new Date()); |
| | | serviceSubtaskDetailMapper.updateServiceSubtaskDetail(detail); |
| | | //命中历史被删副本时置回 del_flag='0',避免再插一份新副本 |
| | | serviceSubtaskDetailMapper.reviveServiceSubtaskDetail(exist.getId()); |
| | | } else { |
| | | //删除误新增detail里的数据 |
| | | ServiceSubtaskDetail serviceSubtaskDetail = new ServiceSubtaskDetail(); |
| | | serviceSubtaskDetail.setSubId(serviceSubtask.getId()); |
| | | List<ServiceSubtaskDetail> serviceSubtaskDetails = serviceSubtaskDetailMapper.selectServiceSubtaskDetailList(serviceSubtaskDetail); |
| | | if (CollectionUtils.isNotEmpty(serviceSubtaskDetails)) { |
| | | for (ServiceSubtaskDetail serviceSubtaskDetail1 : serviceSubtaskDetails) { |
| | | serviceSubtaskDetail1.setDelFlag("1"); |
| | | serviceSubtaskDetailMapper.updateServiceSubtaskDetail(serviceSubtaskDetail1); |
| | | } |
| | | } |
| | | serviceSubtaskDetailMapper.insertServiceSubtaskDetail(detail); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 微信公众号已领取未填写:按题目默认选项补齐当前子任务,并将当前子任务完成。 |
| | | * <p> |
| | | * 所有查询、明细写入、状态更新都只使用当前 subid,避免影响其他任务/子任务。 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Boolean completeWechatClaimedByDefault(ServiceSubtask serviceSubtask) { |
| | | if (serviceSubtask == null || serviceSubtask.getId() == null) { |
| | | return false; |
| | | } |
| | | |
| | | // 重新读取当前子任务,所有判断只针对这一个 subid |
| | | ServiceSubtask current = serviceSubtaskMapper.selectServiceSubtaskById(serviceSubtask.getId()); |
| | | if (current == null) { |
| | | return false; |
| | | } |
| | | // 只处理:丽水中医院、问卷、当前方式公众号、已领取、未提交 |
| | | // 其他医院 orgid 不匹配时直接返回 false,继续走原补偿逻辑。 |
| | | if (!IServiceSubtaskService.WECHAT_DEFAULT_COMPLETE_ORGID.equals(current.getOrgid()) |
| | | || !"2".equals(current.getType()) |
| | | || !"5".equals(current.getCurrentPreachform()) |
| | | || !Long.valueOf(1L).equals(current.getSendstate()) |
| | | || Long.valueOf(1L).equals(current.getSubmit())) { |
| | | return false; |
| | | } |
| | | |
| | | // 查询当前子任务自己的公众号发送方式,必须是已发送(2)或已领取(3) |
| | | ServiceSubtaskPreachform query = new ServiceSubtaskPreachform(); |
| | | query.setSubid(current.getId()); |
| | | query.setTaskid(current.getTaskid()); |
| | | query.setPreachform("5"); |
| | | List<ServiceSubtaskPreachform> preachforms = taskPreachformPort.selectServiceSubtaskPreachformList(query); |
| | | if (CollectionUtils.isEmpty(preachforms)) { |
| | | return false; |
| | | } |
| | | ServiceSubtaskPreachform currentPreachform = preachforms.stream() |
| | | .filter(item -> "2".equals(item.getSendstate()) || "3".equals(item.getSendstate())) |
| | | .findFirst().orElse(null); |
| | | if (currentPreachform == null) { |
| | | return false; |
| | | } |
| | | |
| | | // 只给当前 subid 补齐默认选项;至少有一题默认选项才继续 |
| | | int filled = fillWechatDefaultDetailForCurrentSubtask(current); |
| | | if (filled <= 0) { |
| | | log.info("【微信公众号默认完成】subid:{} 没有可填充的默认选项,跳过", current.getId()); |
| | | return false; |
| | | } |
| | | |
| | | // 当前公众号发送方式:已发送/已领取 -> 完成(9) |
| | | ServiceSubtaskPreachform updatePreachform = new ServiceSubtaskPreachform(); |
| | | updatePreachform.setId(currentPreachform.getId()); |
| | | updatePreachform.setSubid(current.getId()); |
| | | updatePreachform.setPreachform("5"); |
| | | updatePreachform.setOrgid(current.getOrgid()); |
| | | updatePreachform.setRemark("已领取未填写,按默认选项完成"); |
| | | int preachformUpdated = taskPreachformPort.updateWechatPreachformComplete(updatePreachform); |
| | | if (preachformUpdated == 0) { |
| | | throw new BaseException("微信公众号默认完成失败:发送方式状态已变化,subid=" + current.getId()); |
| | | } |
| | | |
| | | // 当前子任务:已领取(1) -> 完成(6),只更新这一个 subid |
| | | ServiceSubtask complete = new ServiceSubtask(); |
| | | complete.setId(current.getId()); |
| | | complete.setOrgid(current.getOrgid()); |
| | | complete.setFinishtime(new Date()); |
| | | complete.setResult("success"); |
| | | complete.setRemark("已领取未填写,按默认选项完成"); |
| | | int subtaskUpdated = serviceSubtaskMapper.updateWechatDefaultComplete(complete); |
| | | if (subtaskUpdated == 0) { |
| | | throw new BaseException("微信公众号默认完成失败:子任务状态已变化,subid=" + current.getId()); |
| | | } |
| | | |
| | | log.info("【微信公众号默认完成】subid:{},已按默认选项完成,填充题目数:{}", current.getId(), filled); |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 仅给当前子任务补齐问卷单选/多选题中 default_value=2 的选项。 |
| | | * 已有非空答案的题目跳过,不覆盖患者已填写内容。 |
| | | * |
| | | * @return 实际填充(或此前已由系统默认填充)的题目数 |
| | | */ |
| | | private int fillWechatDefaultDetailForCurrentSubtask(ServiceSubtask serviceSubtask) { |
| | | Long templateId = serviceSubtask.getTemplateid(); |
| | | ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid()); |
| | | if (templateId == null && serviceTask != null) { |
| | | templateId = serviceTask.getTemplateid(); |
| | | } |
| | | if (templateId == null) { |
| | | return 0; |
| | | } |
| | | |
| | | SvyTaskTemplateScript scriptQuery = new SvyTaskTemplateScript(); |
| | | scriptQuery.setTemplateID(templateId); |
| | | List<SvyTaskTemplateScript> scripts = svyTaskTemplateScriptService.selectSvyTaskTemplateScriptList(scriptQuery); |
| | | if (CollectionUtils.isEmpty(scripts)) { |
| | | return 0; |
| | | } |
| | | |
| | | int filled = 0; |
| | | for (SvyTaskTemplateScript script : scripts) { |
| | | SvyTaskTemplateScriptVO scriptVO = DtoConversionUtils.sourceToTarget(script, SvyTaskTemplateScriptVO.class); |
| | | SvyTaskTemplateScriptVO scriptWithOptions = svyTaskTemplateScriptService.selectInfoByCondition(scriptVO); |
| | | if (scriptWithOptions == null |
| | | || scriptWithOptions.getId() == null |
| | | || CollectionUtils.isEmpty(scriptWithOptions.getSvyTaskTemplateTargetoptions()) |
| | | || (!"1".equals(scriptWithOptions.getScriptType()) && !"2".equals(scriptWithOptions.getScriptType()))) { |
| | | continue; |
| | | } |
| | | |
| | | List<SvyTaskTemplateTargetoption> options = scriptWithOptions.getSvyTaskTemplateTargetoptions(); |
| | | List<SvyTaskTemplateTargetoption> defaultOptions = options.stream() |
| | | .filter(option -> option.getDefaultValue() != null && option.getDefaultValue() == 2L) |
| | | .filter(option -> StringUtils.isNotBlank(option.getOptioncontent())) |
| | | .collect(Collectors.toList()); |
| | | if (CollectionUtils.isEmpty(defaultOptions)) { |
| | | continue; |
| | | } |
| | | |
| | | // 已有非空答案则不覆盖,避免影响患者已填写内容 |
| | | ServiceSubtaskDetail detailQuery = new ServiceSubtaskDetail(); |
| | | detailQuery.setSubId(serviceSubtask.getId()); |
| | | detailQuery.setScriptid(scriptWithOptions.getId()); |
| | | List<ServiceSubtaskDetail> existingDetails = serviceSubtaskDetailMapper.selectServiceSubtaskDetailList(detailQuery); |
| | | ServiceSubtaskDetail existing = CollectionUtils.isNotEmpty(existingDetails) ? existingDetails.get(0) : null; |
| | | if (existing != null |
| | | && (StringUtils.isNotBlank(existing.getAsrtext()) || StringUtils.isNotBlank(existing.getMatchedtext()))) { |
| | | // 之前已由系统默认填充过的,计入已填充,保证重试幂等 |
| | | if ("DEFAULT".equals(existing.getOperate())) { |
| | | filled++; |
| | | } |
| | | continue; |
| | | } |
| | | |
| | | String targetValue = options.stream() |
| | | .map(SvyTaskTemplateTargetoption::getOptioncontent) |
| | | .filter(StringUtils::isNotBlank) |
| | | .collect(Collectors.joining("&")); |
| | | String defaultValue = defaultOptions.stream() |
| | | .map(SvyTaskTemplateTargetoption::getOptioncontent) |
| | | .collect(Collectors.joining("&")); |
| | | |
| | | ServiceSubtaskDetail detail = new ServiceSubtaskDetail(); |
| | | detail.setSubId(serviceSubtask.getId()); |
| | | detail.setTaskid(serviceSubtask.getTaskid()); |
| | | detail.setPatid(serviceSubtask.getPatid()); |
| | | detail.setScriptid(scriptWithOptions.getId()); |
| | | detail.setTemplatequestionnum(scriptWithOptions.getId()); |
| | | detail.setQuestiontext(scriptWithOptions.getScriptContent()); |
| | | detail.setCategoryid(scriptWithOptions.getCategoryid()); |
| | | detail.setTargetid(scriptWithOptions.getTargetid() != null ? scriptWithOptions.getTargetid().intValue() : null); |
| | | detail.setValueType(scriptWithOptions.getScriptType()); |
| | | detail.setTargetvalue(targetValue); |
| | | detail.setAsrtext(defaultValue); |
| | | detail.setMatchedtext(defaultValue); |
| | | detail.setOperate("DEFAULT"); |
| | | detail.setTemplateType(2); |
| | | detail.setLibTemplateid(serviceSubtask.getLibtemplateid() != null ? serviceSubtask.getLibtemplateid().intValue() : null); |
| | | detail.setTemplateid(templateId.toString()); |
| | | detail.setGuid(scriptWithOptions.getGuid()); |
| | | detail.setOrgid(StringUtils.isNotEmpty(scriptWithOptions.getOrgid()) ? scriptWithOptions.getOrgid() |
| | | : (serviceTask != null ? serviceTask.getOrgid() : null)); |
| | | detail.setCreateTime(new Date()); |
| | | detail.setUpdateTime(new Date()); |
| | | |
| | | if (existing != null) { |
| | | detail.setId(existing.getId()); |
| | | serviceSubtaskDetailMapper.updateServiceSubtaskDetail(detail); |
| | | } else { |
| | | serviceSubtaskDetailMapper.insertServiceSubtaskDetail(detail); |
| | | } |
| | | filled++; |
| | | } |
| | | return filled; |
| | | } |
| | | |
| | | @Override |
| | |
| | | String subId = rsaPublicKeyExample.encryptedData(serviceSubtask.getId().toString(), pub_key); |
| | | String taskId = rsaPublicKeyExample.encryptedData(serviceSubtask.getTaskid().toString(), pub_key); |
| | | |
| | | //丽水中医院发送的是微信公众号 |
| | | if (StringUtils.isNotEmpty(serviceSubtask.getOrgid()) && serviceSubtask.getOrgid().equals("47231022633110211A2101")) { |
| | | resultMap = wechatSubTask(subid); |
| | | return resultMap; |
| | | } |
| | | |
| | | //发送数据的封装 |
| | | ServiceOutPath serviceOutPath = new ServiceOutPath(); |
| | | serviceOutPath.setParam1(taskId); |
| | |
| | | serviceOutPath.setCreateTime(new Date()); |
| | | serviceOutPath.setOrgid(serviceSubtask.getOrgid()); |
| | | iServiceOutPathService.insertServiceOutPath(serviceOutPath); |
| | | String format = String.format("%03X", serviceOutPath.getId()); |
| | | String format = ShortCode.encode(serviceOutPath.getId()); |
| | | String url = ""; |
| | | SendMagParam sendMagParam = new SendMagParam(); |
| | | sendMagParam.setType("4"); |
| | |
| | | |
| | | // 参数值经端口取用(原为 sysConfigMapper.checkConfigKeyUnique + getConfigValue)。 |
| | | // 语义等价:原判定"行存在"、这里判定"值非 null",而 sys_config.config_value 是 |
| | | // varchar(500) NOT NULL DEFAULT '' ⇒ 二者不可能不同(详见 TaskSysConfigPort javadoc)。 |
| | | String smsContemt = taskSysConfigPort.findConfigValueByKey("wj.sms.template"); |
| | | // varchar(500) NOT NULL DEFAULT '' ⇒ 二者不可能不同(详见 ISysConfigService javadoc)。 |
| | | String smsContemt = sysConfigService.selectConfigByKey("wj.sms.template"); |
| | | |
| | | if (smsContemt != null) { |
| | | sendMagParam.setContent(smsContemt.replace("${wjurl}", sendMagParam.getUrl())); |
| | |
| | | serviceSubtask.setCurrentPreachform("4"); |
| | | } |
| | | } |
| | | return resultMap; |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> weChatSubTask(Long subid) { |
| | | log.info("smsSubTask的入参为:{}", subid); |
| | | Map<String, Object> resultMap = new HashMap<>(); |
| | | resultMap.put("code", "500"); |
| | | resultMap.put("msg", "发送失败"); |
| | | |
| | | //获取患者的基本信息 |
| | | ServiceSubtask serviceSubtask = selectServiceSubtaskById(subid); |
| | | RSAPublicKeyExample rsaPublicKeyExample = new RSAPublicKeyExample(); |
| | | String patid = rsaPublicKeyExample.encryptedData(serviceSubtask.getPatid().toString(), pub_key); |
| | | String subId = rsaPublicKeyExample.encryptedData(serviceSubtask.getId().toString(), pub_key); |
| | | String taskId = rsaPublicKeyExample.encryptedData(serviceSubtask.getTaskid().toString(), pub_key); |
| | | |
| | | //丽水中医院发送的是微信公众号 |
| | | resultMap = wechatSubTask(subid); |
| | | return resultMap; |
| | | } |
| | | |
| | |
| | | serviceOutPath.setCreateTime(new Date()); |
| | | serviceOutPath.setOrgid(serviceSubtask.getOrgid()); |
| | | iServiceOutPathService.insertServiceOutPath(serviceOutPath); |
| | | String format = String.format("%03X", serviceOutPath.getId()); |
| | | String format = ShortCode.encode(serviceOutPath.getId()); |
| | | serviceOutPath.setRadix(format); |
| | | serviceOutPath.setUpdateTime(new Date()); |
| | | serviceOutPath.setUrl(url); |
| | | iServiceOutPathService.updateServiceOutPath(serviceOutPath); |
| | | |
| | | //his患者id校验(与原实现一致:先校验这一项) |
| | | if (StringUtils.isEmpty(serviceSubtask.getPatidHis())) { |
| | | |
| | | if (patArchive == null) { |
| | | resultMap.put("code", -1); |
| | | resultMap.put("message", "发送异常:" + "【公众号】未查询到患者档案信息,无法通过公众号发送"); |
| | | return resultMap; |
| | | } |
| | | |
| | | // his系统的患者id取自患者档案pat_archive.patid_his, |
| | | // service_subtask查询未关联该字段(serviceSubtask.getPatidHis()恒为空),不能用于校验 |
| | | if (StringUtils.isEmpty(patArchive.getPatidHis())) { |
| | | resultMap.put("code", -1); |
| | | resultMap.put("message", "发送异常:" + "【公众号】公众号发送失败,his系统的患者id为空"); |
| | | return resultMap; |
| | |
| | | return resultMap; |
| | | } |
| | | |
| | | private Boolean saveServiceSubtaskDetail(ServiceSubtask serviceSubtask) { |
| | | Boolean result = true; |
| | | //3.4 如果3.1或3.2为true的话,根据type判断什么类型 |
| | | if (serviceSubtask.getType().equals("1")) { |
| | | //3.3.1 如果type是 1 ,则用当前serviceSubtask的templateid去ivr_task_temaplate_script中获取问题和选项,将问题和选项的默认值放到service_detail中 |
| | | IvrTaskTemplateScript ivrTaskTemplateScript = new IvrTaskTemplateScript(); |
| | | ivrTaskTemplateScript.setTemplateID(serviceSubtask.getTemplateid()); |
| | | List<IvrTaskTemplateScript> ivrTaskTemplateScripts = iIvrTaskTemplateScriptService.selectIvrTaskTemplateScriptList(ivrTaskTemplateScript); |
| | | for (IvrTaskTemplateScript its : ivrTaskTemplateScripts) { |
| | | IvrTaskTemplateScriptVO ivrTaskTemplateScriptVO = DtoConversionUtils.sourceToTarget(its, IvrTaskTemplateScriptVO.class); |
| | | IvrTaskTemplateTargetoption ivrTaskTemplateTargetoption = new IvrTaskTemplateTargetoption(); |
| | | ivrTaskTemplateTargetoption.setScriptid(ivrTaskTemplateScriptVO.getId()); |
| | | List<IvrTaskTemplateTargetoption> ivrTaskTemplateTargetoptions = iIvrTaskTemplateTargetoptionService.selectIvrTaskTemplateTargetoptionList(ivrTaskTemplateTargetoption); |
| | | ivrTaskTemplateScriptVO.setIvrTaskScriptTargetoptionList(ivrTaskTemplateTargetoptions); |
| | | Integer integer = setSFDetailInfo(ivrTaskTemplateScriptVO, serviceSubtask); |
| | | if (Objects.isNull(integer) || integer == 0) result = false; |
| | | } |
| | | /* |
| | | * 2026-10-21 已删除 saveServiceSubtaskDetail(...)。 |
| | | * 它原来的语义是"边算默认值边落库,最后返回布尔值",调用方在返回 false 时把该 sub_id 下的 |
| | | * 全部明细置 del_flag='1'(连患者真实作答一起删)。 |
| | | * 现在拆成 buildDefaultDetails(...)(只算不写) + persistDefaultDetails(...)(只增不删), |
| | | * 任何一题缺默认值就整份跳过,不会再产生"删除"动作。 |
| | | */ |
| | | |
| | | } else if (serviceSubtask.getType().equals("2")) { |
| | | //3.3.2 如果type是 2,则用当前serviceSubtask的templateid去svy_task_temaplate_script中获取问题和选项,将问题和选项的默认值放到service_detail中 |
| | | SvyTaskTemplateScript svyTaskTemplateScript = new SvyTaskTemplateScript(); |
| | | svyTaskTemplateScript.setTemplateID(serviceSubtask.getTemplateid()); |
| | | List<SvyTaskTemplateScript> svyTaskTemplateScripts = svyTaskTemplateScriptService.selectSvyTaskTemplateScriptList(svyTaskTemplateScript); |
| | | for (SvyTaskTemplateScript taskTemplateScript : svyTaskTemplateScripts) { |
| | | SvyTaskTemplateScriptVO svyTaskTemplateScriptVO = DtoConversionUtils.sourceToTarget(taskTemplateScript, SvyTaskTemplateScriptVO.class); |
| | | SvyTaskTemplateScriptVO svyTaskTemplateScriptVO1 = svyTaskTemplateScriptService.selectInfoByCondition(svyTaskTemplateScriptVO); |
| | | Integer integer = setWJDetailInfo(svyTaskTemplateScriptVO1, serviceSubtask); |
| | | if (Objects.isNull(integer) || integer == 0) result = false; |
| | | } |
| | | /** |
| | | * 按「问卷模板」构造一条"默认值明细"(<b>只计算、不落库</b>) |
| | | * <p> |
| | | * 2026-10-21 由 {@code setWJDetailInfo}(算完就直接 insert/update,失败返回 0)改造而来: |
| | | * 现在只负责把实体拼出来,由 {@link #persistDefaultDetails} 统一落库。 |
| | | * |
| | | * @return 明细;该题没配默认值(单/多选题一个默认选项都没有)时返回 {@code null},表示整份问卷不处理 |
| | | */ |
| | | private ServiceSubtaskDetail buildWJDetail(SvyTaskTemplateScriptVO svyTaskTemplateScriptVO, ServiceSubtask serviceSubtask) { |
| | | if (svyTaskTemplateScriptVO == null) { |
| | | return null; |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private Integer setWJDetailInfo(SvyTaskTemplateScriptVO svyTaskTemplateScriptVO, ServiceSubtask serviceSubtask) { |
| | | log.info("-----setWJDetailInfo的入参为:{}", svyTaskTemplateScriptVO); |
| | | log.info("-----buildWJDetail的入参为:{}", svyTaskTemplateScriptVO); |
| | | |
| | | ServiceSubtaskDetailVO serviceSubtaskDetailVO = new ServiceSubtaskDetailVO(); |
| | | if (svyTaskTemplateScriptVO.getScriptType().equals("1") || svyTaskTemplateScriptVO.getScriptType().equals("2")) { |
| | | if ("1".equals(svyTaskTemplateScriptVO.getScriptType()) || "2".equals(svyTaskTemplateScriptVO.getScriptType())) { |
| | | //1、2为单选或多选 |
| | | for (SvyTaskTemplateTargetoption svyTaskTemplateTargetoption : svyTaskTemplateScriptVO.getSvyTaskTemplateTargetoptions()) { |
| | | //将问题选项记录下来 |
| | |
| | | } |
| | | |
| | | } |
| | | //如果asrtext和matchedtext都为空,则没有默认值,则不进行处理 |
| | | //如果asrtext和matchedtext都为空,则没有默认值,则不进行处理(返回 null = 整份问卷跳过) |
| | | if (StringUtils.isEmpty(serviceSubtaskDetailVO.getAsrtext()) && StringUtils.isEmpty(serviceSubtaskDetailVO.getMatchedtext())) { |
| | | return 0; |
| | | return null; |
| | | } |
| | | } |
| | | ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid()); |
| | |
| | | } |
| | | serviceSubtaskDetailVO.setSubId(serviceSubtask.getId()); |
| | | serviceSubtaskDetailVO.setTemplatequestionnum(svyTaskTemplateScriptVO.getId()); |
| | | ServiceSubtaskDetail serviceSubtaskDetail = DtoConversionUtils.sourceToTarget(serviceSubtaskDetailVO, ServiceSubtaskDetail.class); |
| | | |
| | | //先用subid和scriptid查询一下是否存在,如果存在则更新,不存在则插入 |
| | | ServiceSubtaskDetailDTO existServiceSubtaskDetail = serviceSubtaskDetailMapper.isExistServiceSubtaskDetail(serviceSubtaskDetailVO.getSubId(), serviceSubtaskDetailVO.getScriptid()); |
| | | if (ObjectUtils.isNotEmpty(existServiceSubtaskDetail)) { |
| | | serviceSubtaskDetail.setId(existServiceSubtaskDetail.getId()); |
| | | serviceSubtaskDetail.setUpdateTime(new Date()); |
| | | return serviceSubtaskDetailMapper.updateServiceSubtaskDetail(serviceSubtaskDetail); |
| | | } else { |
| | | return serviceSubtaskDetailMapper.insertServiceSubtaskDetail(serviceSubtaskDetail); |
| | | } |
| | | return DtoConversionUtils.sourceToTarget(serviceSubtaskDetailVO, ServiceSubtaskDetail.class); |
| | | } |
| | | |
| | | private Integer setSFDetailInfo(IvrTaskTemplateScriptVO ivrTaskTemplateScriptVO, ServiceSubtask serviceSubtask) { |
| | | |
| | | /** |
| | | * 按「电话模板」构造一条"默认值明细"(<b>只计算、不落库</b>),语义同 {@link #buildWJDetail} |
| | | */ |
| | | private ServiceSubtaskDetail buildSFDetail(IvrTaskTemplateScriptVO ivrTaskTemplateScriptVO, ServiceSubtask serviceSubtask) { |
| | | if (ivrTaskTemplateScriptVO == null) { |
| | | return null; |
| | | } |
| | | ServiceSubtaskDetailVO serviceSubtaskDetailVO = new ServiceSubtaskDetailVO(); |
| | | if (ivrTaskTemplateScriptVO.getScriptType() != null && (ivrTaskTemplateScriptVO.getScriptType().equals("1") || ivrTaskTemplateScriptVO.getScriptType().equals("2"))) { |
| | | //1、2为单选或多选 |
| | |
| | | serviceSubtaskDetailVO.setMatchedtext(StringUtils.isEmpty(serviceSubtaskDetailVO.getMatchedtext()) ? ivrTaskTemplateTargetoption.getTargetvalue() : serviceSubtaskDetailVO.getMatchedtext() + "&" + ivrTaskTemplateTargetoption.getTargetvalue()); |
| | | } |
| | | } |
| | | //如果asrtext和matchedtext都为空,则没有默认值,不进行处理 |
| | | //如果asrtext和matchedtext都为空,则没有默认值,不进行处理(返回 null = 整份问卷跳过) |
| | | if (StringUtils.isEmpty(serviceSubtaskDetailVO.getAsrtext()) && StringUtils.isEmpty(serviceSubtaskDetailVO.getMatchedtext())) { |
| | | return 0; |
| | | return null; |
| | | } |
| | | } else if (ivrTaskTemplateScriptVO.getScriptType() == null) { |
| | | return null; |
| | | } |
| | | |
| | | serviceSubtaskDetailVO.setTargetid(ivrTaskTemplateScriptVO.getTargetid() != null ? ivrTaskTemplateScriptVO.getTargetid().intValue() : null); |
| | |
| | | serviceSubtaskDetailVO.setTemplateType(1); |
| | | serviceSubtaskDetailVO.setTemplateid(ivrTaskTemplateScriptVO.getTemplateID() == null ? null : ivrTaskTemplateScriptVO.getTemplateID().toString()); |
| | | serviceSubtaskDetailVO.setLibTemplateid(serviceSubtask.getLibtemplateid() != null ? serviceSubtask.getLibtemplateid().intValue() : null); |
| | | ServiceSubtaskDetail serviceSubtaskDetail = DtoConversionUtils.sourceToTarget(serviceSubtaskDetailVO, ServiceSubtaskDetail.class); |
| | | //先用subid和scriptid查询一下是否存在,如果存在则更新,不存在则插入 |
| | | ServiceSubtaskDetailDTO existServiceSubtaskDetail = serviceSubtaskDetailMapper.isExistServiceSubtaskDetail(serviceSubtaskDetailVO.getSubId(), serviceSubtaskDetailVO.getScriptid()); |
| | | if (ObjectUtils.isNotEmpty(existServiceSubtaskDetail)) { |
| | | serviceSubtaskDetail.setId(existServiceSubtaskDetail.getId()); |
| | | serviceSubtaskDetail.setUpdateTime(new Date()); |
| | | return serviceSubtaskDetailMapper.updateServiceSubtaskDetail(serviceSubtaskDetail); |
| | | } else { |
| | | return serviceSubtaskDetailMapper.insertServiceSubtaskDetail(serviceSubtaskDetail); |
| | | } |
| | | return DtoConversionUtils.sourceToTarget(serviceSubtaskDetailVO, ServiceSubtaskDetail.class); |
| | | } |
| | | |
| | | |
| | | public String scanGenerateSubtask(ServiceTask serviceTaskVo) { |
| | | String content = ""; |
| | |
| | | serviceOutPath.setCreateTime(new Date()); |
| | | serviceOutPath.setOrgid(serviceTask.getOrgid()); |
| | | iServiceOutPathService.insertServiceOutPath(serviceOutPath); |
| | | String format = String.format("%03X", serviceOutPath.getId()); |
| | | String format = ShortCode.encode(serviceOutPath.getId()); |
| | | serviceOutPath.setRadix(format); |
| | | serviceOutPath.setUpdateTime(new Date()); |
| | | String url = StringUtils.isNotEmpty(req_path) ? ip + ":" + req_path + "/wt?p=" + format : ip + "/wt?p=" + format; |
| | |
| | | // serviceSubtask.setDrcode(patMedInhosp1.getDrcode()); |
| | | // serviceSubtask.setDrname(patMedInhosp1.getDrname()); |
| | | if (StringUtils.isNotEmpty(serviceTaskdept.getDeptType()) && serviceTaskdept.getDeptType().equals("1")) { |
| | | serviceSubtask.setDeptcode(serviceTaskdept.getDeptCode()); |
| | | serviceSubtask.setDeptname(serviceTaskdept.getDeptName()); |
| | | serviceSubtask.setLeaveldeptcode(serviceTaskdept.getDeptCode()); |
| | | serviceSubtask.setLeaveldeptname(serviceTaskdept.getDeptName()); |
| | | } else { |
| | | serviceSubtask.setLeavehospitaldistrictcode(serviceTaskdept.getDeptCode()); |
| | | serviceSubtask.setLeavehospitaldistrictname(serviceTaskdept.getDeptName()); |
| | |
| | | // 这里用任务配置的病区(其次科室)反查 sys_dept.campusid 落库,保证问卷统计能按院区取到。 |
| | | String campusIdByDept = queryCampusIdByDeptCode(serviceSubtask.getLeavehospitaldistrictcode(), serviceTask.getOrgid()); |
| | | if (StringUtils.isEmpty(campusIdByDept)) { |
| | | campusIdByDept = queryCampusIdByDeptCode(serviceSubtask.getDeptcode(), serviceTask.getOrgid()); |
| | | campusIdByDept = queryCampusIdByDeptCode(serviceSubtask.getLeaveldeptcode(), serviceTask.getOrgid()); |
| | | } |
| | | if (StringUtils.isNotEmpty(campusIdByDept)) { |
| | | serviceSubtask.setCampusid(campusIdByDept); |
| | |
| | | if (StringUtils.isEmpty(serviceSubtask.getLeavehospitaldistrictcode()) |
| | | && ("6".equals(serviceSubtask.getServiceType()) || "14".equals(serviceSubtask.getServiceType()))) { |
| | | log.warn("【扫码生成任务】满意度子任务没有病区,按病区筛选的报表将统计不到该答卷:taskid={}, deptcode={}, campusid={}", |
| | | serviceSubtask.getTaskid(), serviceSubtask.getDeptcode(), serviceSubtask.getCampusid()); |
| | | serviceSubtask.getTaskid(), serviceSubtask.getLeaveldeptcode(), serviceSubtask.getCampusid()); |
| | | } |
| | | return serviceSubtask; |
| | | } |
| | |
| | | return null; |
| | | } |
| | | try { |
| | | SysDept sysDept = taskSysDeptPort.selectDeptByCode(deptCode, orgid); |
| | | SysDept sysDept = sysDept2Mapper.selectDeptByCode(deptCode, orgid); |
| | | return sysDept == null ? null : sysDept.getCampusid(); |
| | | } catch (Exception e) { |
| | | log.warn("按编码反查院区失败:deptCode={}, orgid={}, err={}", deptCode, orgid, e.getMessage()); |