| | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.github.pagehelper.ISelect; |
| | | import com.ruoyi.common.annotation.AddOrgId; |
| | | import com.ruoyi.common.annotation.Anonymous; |
| | | import com.ruoyi.common.annotation.Log; |
| | | import com.ruoyi.common.constant.HttpStatus; |
| | | import com.ruoyi.common.core.controller.BaseController; |
| | | import com.ruoyi.common.core.domain.AjaxResult; |
| | | import com.ruoyi.common.core.domain.entity.SysDept; |
| | | import com.ruoyi.common.core.domain.entity.SysUser; |
| | | import com.ruoyi.common.core.domain.entity.SysUserDept; |
| | | import com.ruoyi.common.core.domain.model.LoginUser; |
| | |
| | | import com.ruoyi.task.domain.VO.TransferDeptVO; |
| | | import com.ruoyi.task.domain.ServiceSubtaskExport; |
| | | import com.ruoyi.system.service.ISysConfigService; |
| | | import com.ruoyi.system.service.ISysDeptService; |
| | | import com.ruoyi.system.service.ISysUserDeptService; |
| | | |
| | | /** |
| | |
| | | |
| | | @Autowired |
| | | private ISysConfigService sysConfigService; |
| | | |
| | | @Autowired |
| | | private ISysDeptService sysDeptService; |
| | | |
| | | /** |
| | | * 任务运维触发能力。 |
| | |
| | | serviceSubtaskCountReq.setPageSize(null); |
| | | List<ServiceSubtaskStatistic> sfStatistics = serviceSubtaskService.getSfStatistics(serviceSubtaskCountReq); |
| | | return getDataTable4(CollectionUtils.isEmpty(sfStatistics) ? sfStatistics.size() : 0, serviceSubtaskService.getSfStatistics(serviceSubtaskCountReq)); |
| | | } |
| | | |
| | | // ===================================================================================== |
| | | // 免登录随访统计(供 public/html/sfstat.html 独立页面使用,@Anonymous 即 SecurityConfig 的 permitAll) |
| | | // 口径与 /getSfStatistics 完全一致,区别只有两点: |
| | | // 1. 不依赖登录用户,orgid / campusid 由请求体显式传入; |
| | | // 2. 未指定病区(科室)时,取该机构(院区)下的全部病区(科室),而非当前登录用户的权限范围。 |
| | | // ===================================================================================== |
| | | |
| | | /** |
| | | * 免登录:随访统计(首次 / 再次) |
| | | */ |
| | | @Anonymous |
| | | @PostMapping("/publicSfStatistics") |
| | | public Map<String, Object> publicSfStatistics(@RequestBody ServiceSubtaskCountReq serviceSubtaskCountReq) { |
| | | if (CollectionUtils.isEmpty(serviceSubtaskCountReq.getServiceType())) { |
| | | return error("服务类型不能为空"); |
| | | } |
| | | if (StringUtils.isEmpty(serviceSubtaskCountReq.getOrgid())) { |
| | | return error("缺少机构参数 orgid"); |
| | | } |
| | | Integer statisticaltype = serviceSubtaskCountReq.getStatisticaltype(); |
| | | if (statisticaltype != null && statisticaltype == 1) { |
| | | List<String> wardCodes = isAllSelected(serviceSubtaskCountReq.getLeavehospitaldistrictcodes()) |
| | | ? listDeptCodesByType(serviceSubtaskCountReq.getOrgid(), serviceSubtaskCountReq.getCampusid(), "2") |
| | | : serviceSubtaskCountReq.getLeavehospitaldistrictcodes(); |
| | | if (CollectionUtils.isEmpty(wardCodes)) { |
| | | return getDataTable4(0, new ArrayList<>()); |
| | | } |
| | | serviceSubtaskCountReq.setLeavehospitaldistrictcodes(wardCodes); |
| | | serviceSubtaskCountReq.setDeptcodes(null); |
| | | } else if (statisticaltype != null && statisticaltype == 2) { |
| | | List<String> deptCodes = isAllSelected(serviceSubtaskCountReq.getDeptcodes()) |
| | | ? listDeptCodesByType(serviceSubtaskCountReq.getOrgid(), serviceSubtaskCountReq.getCampusid(), "1") |
| | | : serviceSubtaskCountReq.getDeptcodes(); |
| | | if (CollectionUtils.isEmpty(deptCodes)) { |
| | | return getDataTable4(0, new ArrayList<>()); |
| | | } |
| | | serviceSubtaskCountReq.setDeptcodes(deptCodes); |
| | | serviceSubtaskCountReq.setLeavehospitaldistrictcodes(null); |
| | | } |
| | | String followUpCountStyle = sysConfigService.selectConfigByKey("followUpCountStyle", serviceSubtaskCountReq.getOrgid()); |
| | | if (ObjectUtils.isNotEmpty(followUpCountStyle)) { |
| | | serviceSubtaskCountReq.setFollowUpCountStyle(followUpCountStyle); |
| | | } else { |
| | | serviceSubtaskCountReq.setFollowUpCountStyle("1"); |
| | | } |
| | | if (serviceSubtaskCountReq.getRateDay() == null) { |
| | | serviceSubtaskCountReq.setRateDay(7); |
| | | } |
| | | serviceSubtaskCountReq.setPageNum(null); |
| | | serviceSubtaskCountReq.setPageSize(null); |
| | | List<ServiceSubtaskStatistic> sfStatistics = serviceSubtaskService.getSfStatistics(serviceSubtaskCountReq); |
| | | return getDataTable4(CollectionUtils.isEmpty(sfStatistics) ? 0 : sfStatistics.size(), sfStatistics); |
| | | } |
| | | |
| | | /** |
| | | * 免登录:随访统计筛选项(病区列表 / 科室列表) |
| | | * |
| | | * @param orgid 机构ID,必填 |
| | | * @param campusid 院区ID,可为空;为空时取该机构全部院区 |
| | | */ |
| | | @Anonymous |
| | | @GetMapping("/publicSfStatisticsOptions") public AjaxResult publicSfStatisticsOptions(@RequestParam(value = "orgid", required = false) String orgid, |
| | | @RequestParam(value = "campusid", required = false) String campusid) { |
| | | if (StringUtils.isEmpty(orgid)) { |
| | | return error("缺少机构参数 orgid"); |
| | | } |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("orgid", orgid); |
| | | result.put("campusid", campusid); |
| | | result.put("wards", listDepts(orgid, campusid, "2")); |
| | | result.put("depts", listDepts(orgid, campusid, "1")); |
| | | return success(result); |
| | | } |
| | | |
| | | /** |
| | | * 免登录:延续护理统计(口径与 /getContinueNerseCount 一致,orgid / campusid 由请求体传入) |
| | | */ |
| | | @Anonymous |
| | | @PostMapping("/publicContinueNerseCount") |
| | | public AjaxResult publicContinueNerseCount(@RequestBody ServiceSubtaskCotinueCountVO serviceSubtaskCotinueCountVO) { |
| | | if (StringUtils.isEmpty(serviceSubtaskCotinueCountVO.getOrgid())) { |
| | | return error("缺少机构参数 orgid"); |
| | | } |
| | | List<String> districts = serviceSubtaskCotinueCountVO.getLeavehospitaldistrictcodes(); |
| | | List<String> depts = serviceSubtaskCotinueCountVO.getDeptcodes(); |
| | | boolean hasDistrict = !CollectionUtils.isEmpty(districts) && !districts.contains("all"); |
| | | boolean hasDept = !CollectionUtils.isEmpty(depts) && !depts.contains("all"); |
| | | if (hasDistrict && hasDept) { |
| | | return error("病区Code集合与科室Code集合互斥,不能同时传入"); |
| | | } |
| | | if (!hasDistrict && !hasDept) { |
| | | // 未指定病区/科室:按该机构(院区)下全部病区统计 |
| | | List<String> wardCodes = listDeptCodesByType(serviceSubtaskCotinueCountVO.getOrgid(), |
| | | serviceSubtaskCotinueCountVO.getCampusid(), "2"); |
| | | if (CollectionUtils.isEmpty(wardCodes)) { |
| | | return success(new LinkedHashMap<>()); |
| | | } |
| | | serviceSubtaskCotinueCountVO.setLeavehospitaldistrictcodes(wardCodes); |
| | | serviceSubtaskCotinueCountVO.setDeptcodes(null); |
| | | } else if (hasDistrict) { |
| | | serviceSubtaskCotinueCountVO.setDeptcodes(null); |
| | | } else { |
| | | serviceSubtaskCotinueCountVO.setLeavehospitaldistrictcodes(null); |
| | | } |
| | | return success(serviceSubtaskService.getContinueNurseCount(serviceSubtaskCotinueCountVO)); |
| | | } |
| | | |
| | | /** |
| | | * 前端未选择具体病区/科室(或选择了 all)时,视为“全部” |
| | | */ |
| | | private boolean isAllSelected(List<String> codes) { |
| | | return CollectionUtils.isEmpty(codes) || codes.contains("all"); |
| | | } |
| | | |
| | | private List<String> listDeptCodesByType(String orgid, String campusid, String deptType) { |
| | | List<String> codes = new ArrayList<>(); |
| | | for (Map<String, String> item : listDepts(orgid, campusid, deptType)) { |
| | | codes.add(item.get("code")); |
| | | } |
| | | return codes; |
| | | } |
| | | |
| | | /** |
| | | * 查询机构(院区)下指定类型的部门:deptType=2 病区,deptType=1 科室 |
| | | */ |
| | | private List<Map<String, String>> listDepts(String orgid, String campusid, String deptType) { |
| | | List<Map<String, String>> result = new ArrayList<>(); |
| | | SysDept query = new SysDept(); |
| | | query.setOrgid(orgid); |
| | | query.setDeptType(deptType); |
| | | if (StringUtils.isNotEmpty(campusid) && !"all".equalsIgnoreCase(campusid)) { |
| | | query.setCampusid(campusid); |
| | | } |
| | | List<SysDept> depts = sysDeptService.selectSysDeptList(query); |
| | | if (CollectionUtils.isEmpty(depts)) { |
| | | return result; |
| | | } |
| | | for (SysDept dept : depts) { |
| | | if (StringUtils.isEmpty(dept.getDeptCode())) { |
| | | continue; |
| | | } |
| | | Map<String, String> item = new LinkedHashMap<>(); |
| | | item.put("code", dept.getDeptCode()); |
| | | item.put("name", dept.getDeptName()); |
| | | result.add(item); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | |
| | | Boolean redisFlag = false; |
| | | if (MapUtils.isNotEmpty(redisMap)) { |
| | | long tCmp = System.currentTimeMillis(); |
| | | ServiceSubtaskEntity oldCondition = (ServiceSubtaskEntity) redisMap.get("searchCondition"); |
| | | ServiceSubtaskEntity oldCondition = convertCachedCondition(redisMap.get("searchCondition")); |
| | | if (ObjectUtils.isNotEmpty(oldCondition) && isSameCondition(oldCondition, serviceSubtaskEntity)) { |
| | | redisFlag = true; |
| | | map = redisMap; |
| | | // 顺手回填转换后的实体,让这条缓存下次能以正确类型反序列化 |
| | | map.put("searchCondition", oldCondition); |
| | | redisCache.setCacheObject(userId + "patItemCount", map, 60, TimeUnit.MINUTES); |
| | | } |
| | | log.info("[patItem耗时] 4.2 条件比对={}ms,命中={}", System.currentTimeMillis() - tCmp, redisFlag); |
| | |
| | | return map; |
| | | } |
| | | |
| | | /** |
| | | * 把 Redis 缓存里的查询条件还原成 {@link ServiceSubtaskEntity}。 |
| | | * <p> |
| | | * 缓存由 {@code FastJson2JsonRedisSerializer} 按 {@code Object.class} 反序列化: |
| | | * 当 {@code searchCondition} 的 {@code @type} 缺失、或指向已迁移的旧包名 |
| | | * (如 {@code com.smartor.domain.entity.ServiceSubtaskEntity})时,fastjson2 会 |
| | | * 静默退化成 {@link com.alibaba.fastjson2.JSONObject},直接强转会抛 |
| | | * {@code ClassCastException}。这里统一转换;转换失败返回 {@code null}, |
| | | * 调用方按缓存未命中处理(重新查库),避免整个接口 500。 |
| | | */ |
| | | private ServiceSubtaskEntity convertCachedCondition(Object cachedCondition) { |
| | | if (cachedCondition == null) { |
| | | return null; |
| | | } |
| | | if (cachedCondition instanceof ServiceSubtaskEntity) { |
| | | return (ServiceSubtaskEntity) cachedCondition; |
| | | } |
| | | try { |
| | | // 不能直接用 TypeUtils.cast / toJavaObject:旧缓存里的 @type 可能指向已迁移的类, |
| | | // fastjson2 会按 @type 去找类并抛 "No suitable ObjectReader found"。 |
| | | // 转成 JSON 字符串后按目标类解析,未开启 SupportAutoType 时 @type 只会被当作普通字段忽略。 |
| | | return com.alibaba.fastjson2.JSON.parseObject( |
| | | com.alibaba.fastjson2.JSON.toJSONString(cachedCondition), |
| | | ServiceSubtaskEntity.class); |
| | | } catch (Exception e) { |
| | | log.warn("【patItemCount】缓存中的查询条件无法还原为 ServiceSubtaskEntity,按缓存未命中处理,实际类型:{}", |
| | | cachedCondition.getClass().getName(), e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 比较两个ServiceSubtaskVO的查询条件是否一致 |