| | |
| | | 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.mapper.ext.SysDept2Mapper; |
| | | |
| | | /** |
| | | * 单一任务(随访)Service业务层处理 |
| | |
| | | private ServiceSubtaskMapper serviceSubtaskMapper; |
| | | |
| | | @Autowired |
| | | private TaskPreachformPort taskPreachformPort; |
| | | private ServiceSubtaskPreachformMapper taskPreachformPort; |
| | | |
| | | @Autowired |
| | | private ServiceSubtaskDetailMapper serviceSubtaskDetailMapper; |
| | |
| | | @Autowired |
| | | private ScheduleSubtaskBuilder scheduleSubtaskBuilder; |
| | | |
| | | @Autowired |
| | | private TaskFtpPort taskFtpPort; |
| | | |
| | | |
| | | |
| | | |
| | |
| | | |
| | | |
| | | @Autowired |
| | | private TaskSysConfigPort taskSysConfigPort; |
| | | private ISysConfigService sysConfigService; |
| | | |
| | | /** |
| | | * 消息发送方工厂(短信/微信公众号),替代原来直接调用各站点发送代码的写法 |
| | |
| | | |
| | | |
| | | @Autowired |
| | | private TaskSysDeptPort taskSysDeptPort; |
| | | private SysDept2Mapper sysDept2Mapper; |
| | | |
| | | @Value("${spring.profiles.active}") |
| | | private String active; |
| | |
| | | |
| | | 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) |
| | |
| | | } |
| | | |
| | | /** |
| | | * 统一「随访病区」口径:一律以【出院记录】{@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()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 新增单一任务(随访) |
| | | * |
| | | * @param serviceSubtask 单一任务(随访) |
| | |
| | | } |
| | | |
| | | serviceSubtask.setCreateTime(DateUtils.getNowDate()); |
| | | // 口径(2026-10-29 业务确认):出院随访的科室/病区一律以出院记录为准 |
| | | alignDischargeWardFromInhosp(serviceSubtask); |
| | | serviceSubtaskMapper.insertServiceSubtask(serviceSubtask); |
| | | //修改住院满意度状态(避免重复查询) |
| | | if (serviceSubtask.getServiceType() != null && "6".equals(serviceSubtask.getServiceType())) { |
| | |
| | | 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) { |
| | | //新增成功后,需要将之前的任务设置为不要再随访(防止重复点,然后重复生成) |
| | |
| | | } |
| | | 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); |
| | |
| | | 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); |
| | | /* |
| | |
| | | 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) { |
| | |
| | | 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()); |
| | |
| | | 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 Map<String, Object> getContinueNurseCount(ServiceSubtaskCotinueCountVO vo) { |
| | | boolean hasDistrict = !CollectionUtils.isEmpty(vo.getLeavehospitaldistrictcodes()); |
| | |
| | | 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()) { |
| | |
| | | |
| | | // 参数值经端口取用(原为 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())); |
| | |
| | | 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()); |