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.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 io.swagger.annotations.ApiOperation;
|
import org.apache.commons.collections4.CollectionUtils;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.web.bind.annotation.*;
|
|
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
|
*
|
* @author smartor
|
* @date 2023-03-24
|
*/
|
@Api(description = "语音任务结果明细")
|
@RestController
|
@RequestMapping("/smartor/serviceSubtaskDetail")
|
public class ServiceSubtaskDetailController extends BaseController {
|
@Autowired
|
private IServiceSubtaskDetailService ServiceSubtaskDetailService;
|
@Autowired
|
private IServiceSubtaskService serviceSubtaskService;
|
|
@Autowired
|
private ISysConfigService configService;
|
|
/**
|
* 查询单个人的语音信息
|
*/
|
@ApiOperation("查询单个人的语音信息通过子任务ID")
|
@PostMapping("/getPersonVoice")
|
public Map<String, Object> getPersonVoice(@RequestBody ServiceSubtaskDetail serviceSubtaskDetail) {
|
Map<String, Object> map = new HashMap<>();
|
if (serviceSubtaskDetail.getSubId() == null) {
|
map.put("code", 500);
|
map.put("msg", "查询失败,请查检入参");
|
} else map = ServiceSubtaskDetailService.selectServiceSubtaskDetailList(serviceSubtaskDetail);
|
return map;
|
}
|
|
/**
|
* 获取语音任务呼叫明细详细信息
|
*/
|
//@PreAuthorize("@ss.hasPermi('smartor:ServiceSubtaskDetail:query')")
|
@ApiOperation("获取任务结果明细详细信息")
|
@GetMapping(value = "/getInfo/{calldetailid}")
|
public AjaxResult getInfo(@PathVariable("calldetailid") String calldetailid) {
|
return success(ServiceSubtaskDetailService.selectServiceSubtaskDetailByCalldetailid(calldetailid));
|
}
|
|
/**
|
* 新增任务结果明细
|
*/
|
//@PreAuthorize("@ss.hasPermi('smartor:ServiceSubtaskDetail:add')")
|
@ApiOperation("新增任务结果明细")
|
@Log(title = "语音任务呼叫明细", businessType = BusinessType.INSERT)
|
@PostMapping("/add")
|
public AjaxResult add(@RequestBody ServiceSubtaskDetail serviceSubtaskDetail) {
|
SysUser user = getLoginUser().getUser();
|
serviceSubtaskDetail.setOrgid(user.getOrgid());
|
return toAjax(ServiceSubtaskDetailService.insertServiceSubtaskDetail(serviceSubtaskDetail));
|
}
|
|
/**
|
* 新增任务结果明细
|
*/
|
//@PreAuthorize("@ss.hasPermi('smartor:ServiceSubtaskDetail:add')")
|
@ApiOperation("批量新增任务结果明细")
|
@Log(title = "语音任务呼叫明细", businessType = BusinessType.INSERT)
|
@PostMapping("/batchAdd")
|
public AjaxResult batchAdd(@RequestBody ServiceSubTaskDetailReq serviceSubTaskDetailReq) {
|
if (CollectionUtils.isEmpty(serviceSubTaskDetailReq.getServiceSubtaskDetailList()) && serviceSubTaskDetailReq.getSubId() == null) {
|
return error("参数不全,请检查入参");
|
}
|
int i = ServiceSubtaskDetailService.batchInsertServiceSubtaskDetail(serviceSubTaskDetailReq.getServiceSubtaskDetailList());
|
Boolean aBoolean = false;
|
if (i == 1) {
|
ServiceSubtask serviceSubtask = new ServiceSubtask();
|
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(aBoolean);
|
}
|
|
/**
|
* 修改语音任务呼叫明细
|
*/
|
//@PreAuthorize("@ss.hasPermi('smartor:ServiceSubtaskDetail:edit')")
|
@Log(title = "语音任务呼叫明细", businessType = BusinessType.UPDATE)
|
@ApiOperation("修改任务结果明细")
|
@PostMapping("/edit")
|
public AjaxResult edit(@RequestBody ServiceSubtaskDetail serviceSubtaskDetail) {
|
return toAjax(ServiceSubtaskDetailService.updateServiceSubtaskDetail(serviceSubtaskDetail));
|
}
|
|
|
/**
|
* 修改语音任务呼叫明细
|
*/
|
//@PreAuthorize("@ss.hasPermi('smartor:ServiceSubtaskDetail:edit')")
|
@Log(title = "语音任务呼叫明细", businessType = BusinessType.UPDATE)
|
@ApiOperation("修改任务结果明细")
|
@PostMapping("/batchEdit")
|
public AjaxResult batchEdit(@RequestBody List<ServiceSubtaskDetail> serviceSubtaskDetails) {
|
if (CollectionUtils.isEmpty(serviceSubtaskDetails)) {
|
return error("需要修改语音详情为空");
|
}
|
for (ServiceSubtaskDetail serviceSubtaskDetail : serviceSubtaskDetails) {
|
ServiceSubtaskDetailService.updateServiceSubtaskDetail(serviceSubtaskDetail);
|
}
|
return toAjax(1);
|
}
|
|
@ApiOperation("修改任务结果明细通过条件")
|
@PostMapping("/editByCondition")
|
public AjaxResult updateSSDByCondition(@RequestBody ServiceSubtaskDetail serviceSubtaskDetail) {
|
return toAjax(ServiceSubtaskDetailService.updateSSDByCondition(serviceSubtaskDetail));
|
}
|
|
/**
|
* 删除语音任务呼叫明细
|
*/
|
//@PreAuthorize("@ss.hasPermi('smartor:ServiceSubtaskDetail:remove')")
|
@Log(title = "语音任务呼叫明细", businessType = BusinessType.DELETE)
|
@GetMapping("/remove/{calldetailids}")
|
public AjaxResult remove(@PathVariable String[] calldetailids) {
|
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>
|
* 综合满意度 = (很满意项数×1 + 满意项数×0.8 + 一般项数×0.6 + 不满意项数×0.4 + 很不满意项数×0.2)
|
* / (很满意项数 + 满意项数 + 一般项数 + 不满意项数 + 很不满意项数) × 100%;
|
* 「未经历」「不适用」等不在五档里的选项既不进分子也不进分母。
|
*/
|
@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);
|
//综合得分:(很满意×1 + 满意×0.8 + 一般×0.6 + 不满意×0.4 + 很不满意×0.2) / 五档项数之和 × 100%
|
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";
|
|
/**
|
* 满意度五档选项(顺序与权重顺序一致)
|
*/
|
private static final String[] MYD_SCORE_OPTIONS = {"很满意", "满意", "一般", "不满意", "很不满意"};
|
|
/**
|
* 满意度五档权重:很满意 1、满意 0.8、一般 0.6、不满意 0.4、很不满意 0.2
|
*/
|
private static final BigDecimal[] MYD_SCORE_WEIGHTS = {
|
new BigDecimal("1"), new BigDecimal("0.8"), new BigDecimal("0.6"), new BigDecimal("0.4"), new BigDecimal("0.2")};
|
|
/**
|
* 计算一道题的满意度综合得分(百分制,保留两位小数)
|
* <p>
|
* 综合满意度 = (很满意项数×1 + 满意项数×0.8 + 一般项数×0.6 + 不满意项数×0.4 + 很不满意项数×0.2)
|
* / (很满意项数 + 满意项数 + 一般项数 + 不满意项数 + 很不满意项数) × 100%
|
* <p>
|
* 权重按选项文案判定(见 {@link #mydScoreOptionIndex(String)}),不再依赖明细的 score 字段:
|
* 历史数据里 score 有 0、「0000000001」这类脏值,按 score 算会把得分固定成 0 或 100。
|
* 「未经历」「不适用」等不在五档里的选项既不进分子也不进分母。
|
*
|
* @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; //分母:五档项数之和
|
|
for (ServiceSubtaskDetailRatioExport detail : exportList) {
|
//多选题一行是多个选项的分值之和,分母只算 1 项,会把得分算高,不计入综合得分
|
if (VALUE_TYPE_MULTIPLE.equals(detail.getValueType())) continue;
|
//「不适用」不在公式的五档里,不计入综合得分
|
if (isNotApplicable(detail)) continue;
|
|
int optionIndex = mydScoreOptionIndex(detail.getOptionresult());
|
BigDecimal count = parseDecimal(detail.getCount());
|
if (optionIndex < 0 || count == null || count.signum() <= 0) continue;
|
|
scoreSum = scoreSum.add(MYD_SCORE_WEIGHTS[optionIndex].multiply(count));
|
countSum = countSum.add(count);
|
}
|
|
if (countSum.signum() <= 0) return "0.00";
|
|
return scoreSum.divide(countSum, 6, RoundingMode.HALF_UP)
|
.multiply(BigDecimal.valueOf(100))
|
.setScale(2, RoundingMode.HALF_UP)
|
.toPlainString();
|
}
|
|
/**
|
* 选项文案 → 满意度五档下标(很满意 0、满意 1、一般 2、不满意 3、很不满意 4),不在五档里返回 -1
|
*/
|
private int mydScoreOptionIndex(String optionresult) {
|
if (StringUtils.isEmpty(optionresult)) return -1;
|
String text = optionresult.trim();
|
for (int i = 0; i < MYD_SCORE_OPTIONS.length; i++) {
|
if (MYD_SCORE_OPTIONS[i].equals(text)) return i;
|
}
|
//兼容「A 很满意」「B.满意」这类带选项序号前缀的历史数据
|
if (text.length() > 2 && (text.charAt(1) == ' ' || text.charAt(1) == '.' || text.charAt(1) == '、')) {
|
String stripped = text.substring(2).trim();
|
for (int i = 0; i < MYD_SCORE_OPTIONS.length; i++) {
|
if (MYD_SCORE_OPTIONS[i].equals(stripped)) return i;
|
}
|
}
|
return -1;
|
}
|
|
/**
|
* 是否「不适用」选项
|
*/
|
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;
|
}
|
}
|
|
}
|