| | |
| | | package com.ruoyi.web.controller.smartor; |
| | | |
| | | import com.ruoyi.common.annotation.AddOrgId; |
| | | import com.ruoyi.common.annotation.Log; |
| | | import com.ruoyi.common.core.controller.BaseController; |
| | | import com.ruoyi.common.core.domain.AjaxResult; |
| | | import com.ruoyi.common.core.domain.entity.SysUser; |
| | | import com.ruoyi.common.core.domain.model.LoginUser; |
| | | import com.ruoyi.common.enums.BusinessType; |
| | | import com.ruoyi.common.utils.DtoConversionUtils; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.smartor.domain.ServiceSubTaskDetailReq; |
| | | import com.smartor.domain.ServiceSubtask; |
| | | import com.smartor.domain.ServiceSubtaskDetail; |
| | | import com.ruoyi.system.service.ISysConfigService; |
| | | import com.smartor.domain.DTO.QuestionResultDTO; |
| | | import com.smartor.domain.*; |
| | | import com.smartor.domain.VO.MydTrendReqVO; |
| | | import com.smartor.domain.VO.MzMydScoreRankVO; |
| | | import com.smartor.domain.VO.QuestionCountKsOrBqVO; |
| | | import com.smartor.domain.VO.SltdMydTotalVO; |
| | | import com.smartor.domain.entity.ServiceSubtaskEntity; |
| | | import com.smartor.service.IServiceSubtaskDetailService; |
| | | import com.smartor.service.IServiceSubtaskService; |
| | | import io.swagger.annotations.Api; |
| | |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static com.ruoyi.framework.datasource.DynamicDataSourceContextHolder.log; |
| | | |
| | | /** |
| | | * 语音任务结果明细Controller |
| | |
| | | private IServiceSubtaskDetailService ServiceSubtaskDetailService; |
| | | @Autowired |
| | | private IServiceSubtaskService serviceSubtaskService; |
| | | |
| | | @Autowired |
| | | private ISysConfigService configService; |
| | | |
| | | /** |
| | | * 查询单个人的语音信息 |
| | |
| | | serviceSubtask.setSendstate(6L); |
| | | serviceSubtask.setId(serviceSubTaskDetailReq.getSubId()); |
| | | serviceSubtask.setRemark("数据补充完成"); |
| | | serviceSubtask.setUpdateTime(new Date()); |
| | | serviceSubtask.setFinishtime(new Date()); |
| | | serviceSubtask.setPreachform("1"); |
| | | serviceSubtask.setResult("success"); |
| | | aBoolean = serviceSubtaskService.updateServiceSubtask(serviceSubtask); |
| | | } |
| | |
| | | return toAjax(ServiceSubtaskDetailService.deleteServiceSubtaskDetailByCalldetailids(calldetailids)); |
| | | } |
| | | |
| | | /** |
| | | * 指标统计 |
| | | */ |
| | | //@PreAuthorize("@ss.hasPermi('smartor:ServiceSubtaskDetail:add')") |
| | | @ApiOperation("指标统计") |
| | | @GetMapping(value = "/countPatByTarget/{targetid}") |
| | | public AjaxResult countPatByTarget(@PathVariable("targetid") Long targetid) { |
| | | return success(ServiceSubtaskDetailService.countPatByTarget(targetid)); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 获取随访详情-指标详情(超链接) |
| | | */ |
| | | @ApiOperation("获取随访详情-指标详情") |
| | | @PostMapping("/getServiceSubtaskDetails") |
| | | public List<Map<String, Object>> getServiceSubtaskDetails(@RequestBody ServiceSubtaskDetailVO serviceSubtaskDetailVO) { |
| | | List<Map<String, Object>> map = new ArrayList<>(); |
| | | map = ServiceSubtaskDetailService.getServiceSubtaskDetails(serviceSubtaskDetailVO); |
| | | return map; |
| | | } |
| | | |
| | | /** |
| | | * 获取随访详情-指标详情(超链接) |
| | | */ |
| | | @ApiOperation("获取随访详情-指标详情(超链接)") |
| | | @PostMapping("/getServiceSubtaskDetailsHyperlink") |
| | | public List<Map<String, Object>> getServiceSubtaskDetailsHyperlink(@RequestBody ServiceSubtaskDetailVO serviceSubtaskDetailVO) { |
| | | LoginUser loginUser = getLoginUser(); |
| | | String orgid = loginUser.getUser().getOrgid(); |
| | | String configKey = serviceSubtaskDetailVO.getConfigKey(); |
| | | if (StringUtils.isNotEmpty(configKey)) { |
| | | String configValue = configService.selectConfigByKey(configKey, orgid); |
| | | serviceSubtaskDetailVO.setConfigValue(configValue); |
| | | if (StringUtils.isEmpty(configValue)) { |
| | | String logInfo = "getSfStatisticsCount-参数 " + configKey + " 未配置, 请配置好后重试"; |
| | | log.error(logInfo); |
| | | } |
| | | } |
| | | List<Map<String, Object>> map = new ArrayList<>(); |
| | | map = ServiceSubtaskDetailService.getServiceSubtaskDetails(serviceSubtaskDetailVO); |
| | | return map; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 省立同德满意度数据统计(按题目统计选项数量/占比,并给出该题的综合得分) |
| | | * <p> |
| | | * 综合满意度 = Σ(选项分值 × 项数) / Σ项数 / 本题最高分值 × 100%, |
| | | * 分值取 1、0.8、0.6、0.4、0.2 五档(很满意、满意、一般、不满意、很不满意); |
| | | * 「不适用」、0 分、空值等既不进分子也不进分母。 |
| | | */ |
| | | @ApiOperation("省立同德满意度数据统计") |
| | | @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid") |
| | | @PostMapping("/sltdMydTotalByScore") |
| | | public Map<String, Object> sltdMydTotalByScore(@RequestBody SltdMydTotalVO sltdMydTotalVO) { |
| | | List<ServiceSubtaskRes> serviceSubtaskResList = null; |
| | | if (CollectionUtils.isNotEmpty(sltdMydTotalVO.getServiceTypeList())) { |
| | | //serviceType可能会出现多个,不想多写方法了,一个一个查询吧 |
| | | for (String st : sltdMydTotalVO.getServiceTypeList()) { |
| | | ServiceSubtaskEntity serviceSubtaskEntity = new ServiceSubtaskEntity(); |
| | | serviceSubtaskEntity.setServiceType(st); |
| | | serviceSubtaskEntity.setEndOutHospTime(sltdMydTotalVO.getEndOutHospTime()); |
| | | serviceSubtaskEntity.setStartOutHospTime(sltdMydTotalVO.getStartOutHospTime()); |
| | | serviceSubtaskEntity.setDeptOrDistrict(sltdMydTotalVO.getDeptOrDistrict()); |
| | | serviceSubtaskEntity.setLeavehospitaldistrictcodes(sltdMydTotalVO.getLeavehospitaldistrictcodes()); |
| | | serviceSubtaskEntity.setLeaveldeptcodes(sltdMydTotalVO.getLeaveldeptcodes()); |
| | | //机构/院区由 @AddOrgId 注入,落到查询条件上,避免跨机构取数 |
| | | serviceSubtaskEntity.setOrgid(sltdMydTotalVO.getOrgid()); |
| | | serviceSubtaskEntity.setCampusid(sltdMydTotalVO.getCampusid()); |
| | | if (serviceSubtaskEntity != null) { |
| | | List<ServiceSubtask> serviceSubtasks = serviceSubtaskService.getServiceSubtasks(serviceSubtaskEntity); |
| | | List<ServiceSubtaskRes> serviceSubtaskRes = DtoConversionUtils.sourceToTarget(serviceSubtasks, ServiceSubtaskRes.class); |
| | | if (CollectionUtils.isNotEmpty(serviceSubtaskResList)) { |
| | | serviceSubtaskResList.addAll(serviceSubtaskRes); |
| | | } else { |
| | | if (CollectionUtils.isEmpty(serviceSubtaskResList)) serviceSubtaskResList = serviceSubtaskRes; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | // 按 questiontext 分组后转换为 QuestionResultVO |
| | | List<QuestionResultDTO> result = new ArrayList<>(); |
| | | if (CollectionUtils.isNotEmpty(serviceSubtaskResList)) { |
| | | List<ServiceSubtaskDetailRatioExport> detailList = serviceSubtaskService.getDetailRatio(sltdMydTotalVO, serviceSubtaskResList); |
| | | |
| | | if (detailList == null) { |
| | | //serviceType 组合不在支持范围内(如同时含随访 2 和满意度 6/14),给明确提示而不是悄悄返回空数组 |
| | | return error("当前 serviceType 组合暂不支持统计问题占比,请分开查询(仅支持随访 2 或满意度 6/14)"); |
| | | } |
| | | if (CollectionUtils.isNotEmpty(detailList)) { |
| | | Map<String, List<ServiceSubtaskDetailRatioExport>> groupedMap = detailList.stream().collect(Collectors.groupingBy(ServiceSubtaskDetailRatioExport::getQuestiontext)); |
| | | |
| | | groupedMap.forEach((questionText, exportList) -> { |
| | | QuestionResultDTO questionResultDTO = new QuestionResultDTO(); |
| | | questionResultDTO.setQeustionText(questionText); |
| | | questionResultDTO.setSubtaskDetailRatioExportList(exportList); |
| | | //综合得分:Σ(分值×项数) / Σ项数 / 本题最高分值 × 100%,不适用/0分/空值不进分子也不进分母 |
| | | questionResultDTO.setAvgScore(calcMydScore(exportList)); |
| | | result.add(questionResultDTO); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | return success(result); |
| | | } |
| | | |
| | | @ApiOperation("省立同德满意度数据统计通过科室或病区") |
| | | @PostMapping("/statQuestionOptionByKsOrBq") |
| | | public Map<String, Object> statQuestionOptionByKsOrBq(@RequestBody QuestionCountKsOrBqVO questionResultVO) { |
| | | return success(serviceSubtaskService.statQuestionOptionByKsOrBq(questionResultVO)); |
| | | } |
| | | |
| | | @ApiOperation("省立同德患者满意度趋势") |
| | | @PostMapping("/mydTrend") |
| | | public Map<String, Object> mydTrend(@RequestBody MydTrendReqVO mydTrendReqVO) { |
| | | return success(serviceSubtaskService.mydTrend(mydTrendReqVO)); |
| | | } |
| | | |
| | | @ApiOperation("省立同德患者满意度维护") |
| | | @PostMapping("/sltdMydTotalByDimension") |
| | | public Map<String, Object> sltdMydTotalByDimension(@RequestBody MydTrendReqVO mydTrendReqVO) { |
| | | return success(serviceSubtaskService.sltdMydTotalByDimension(mydTrendReqVO)); |
| | | } |
| | | |
| | | @ApiOperation("门诊满意度综合得分排名(按科室或病区)") |
| | | @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid") |
| | | @PostMapping("/mzMydScoreRank") |
| | | public Map<String, Object> mzMydScoreRank(@RequestBody MzMydScoreRankVO mzMydScoreRankVO) { |
| | | return success(serviceSubtaskService.mzMydScoreRank(mzMydScoreRankVO)); |
| | | } |
| | | |
| | | /** |
| | | * 多选题的问题类型 |
| | | */ |
| | | private static final String VALUE_TYPE_MULTIPLE = "2"; |
| | | |
| | | /** |
| | | * 计算一道题的满意度综合得分(百分制,保留两位小数) |
| | | * <p> |
| | | * 综合满意度 = Σ(选项分值 × 项数) / Σ项数 / 本题最高分值 × 100% |
| | | * 等价于 Σ(很满意×1 + 满意×0.8 + 一般×0.6 + 不满意×0.4 + 很不满意×0.2) / Σ(五档项数) × 100%。 |
| | | * 用本题最高分值归一化,兼容明细分值存 0-1 权重(1、0.8…)和存 5 分制(5、4…)两种口径。 |
| | | * |
| | | * @param exportList 同一道题目的选项统计明细 |
| | | * @return 百分制综合得分,无有效项数时返回 "0.00" |
| | | */ |
| | | private String calcMydScore(List<ServiceSubtaskDetailRatioExport> exportList) { |
| | | if (CollectionUtils.isEmpty(exportList)) return "0.00"; |
| | | |
| | | BigDecimal scoreSum = BigDecimal.ZERO; //分子:Σ(选项分值 × 项数) |
| | | BigDecimal countSum = BigDecimal.ZERO; //分母:Σ项数 |
| | | BigDecimal maxScore = null; //本题最高分值,用于归一化 |
| | | |
| | | for (ServiceSubtaskDetailRatioExport detail : exportList) { |
| | | //多选题一行是多个选项的分值之和,分母只算 1 项,会把得分算高,不计入综合得分 |
| | | if (VALUE_TYPE_MULTIPLE.equals(detail.getValueType())) continue; |
| | | //「不适用」不在公式的五档里,不计入综合得分 |
| | | if (isNotApplicable(detail)) continue; |
| | | |
| | | BigDecimal score = parseDecimal(detail.getScore()); |
| | | BigDecimal count = parseDecimal(detail.getCount()); |
| | | if (score == null || count == null) continue; |
| | | //0 分(未答/空答)、空数量:既不进分子也不进分母 |
| | | if (score.signum() <= 0 || count.signum() <= 0) continue; |
| | | |
| | | if (maxScore == null || score.compareTo(maxScore) > 0) maxScore = score; |
| | | scoreSum = scoreSum.add(score.multiply(count)); |
| | | countSum = countSum.add(count); |
| | | } |
| | | |
| | | if (maxScore == null || countSum.signum() <= 0) return "0.00"; |
| | | |
| | | return scoreSum.divide(countSum, 6, RoundingMode.HALF_UP) |
| | | .divide(maxScore, 6, RoundingMode.HALF_UP) |
| | | .multiply(BigDecimal.valueOf(100)) |
| | | .setScale(2, RoundingMode.HALF_UP) |
| | | .toPlainString(); |
| | | } |
| | | |
| | | /** |
| | | * 是否「不适用」选项 |
| | | */ |
| | | private boolean isNotApplicable(ServiceSubtaskDetailRatioExport detail) { |
| | | return containsNotApplicable(detail.getOptionresult()) || containsNotApplicable(detail.getTargetvalue()); |
| | | } |
| | | |
| | | private boolean containsNotApplicable(String optionText) { |
| | | if (StringUtils.isEmpty(optionText)) return false; |
| | | String text = optionText.trim(); |
| | | return text.contains("不适用") || text.contains("未答") || text.contains("拒答"); |
| | | } |
| | | |
| | | /** |
| | | * 字符串转 BigDecimal,空值/非法值返回 null |
| | | */ |
| | | private BigDecimal parseDecimal(String value) { |
| | | if (StringUtils.isEmpty(value)) return null; |
| | | try { |
| | | return new BigDecimal(value.trim()); |
| | | } catch (NumberFormatException e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | } |