package com.ruoyi.satisfaction.controller;
import com.ruoyi.common.annotation.AddOrgId;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.satisfaction.config.SatisfactionSwitch;
import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
import com.ruoyi.satisfaction.domain.dto.QuestionResultDTO;
import com.ruoyi.satisfaction.domain.row.SatisfactionSubtaskQuery;
import com.ruoyi.satisfaction.domain.row.SatisfactionSubtaskRef;
import com.ruoyi.satisfaction.domain.vo.MydTrendReqVO;
import com.ruoyi.satisfaction.domain.vo.MzMydScoreRankVO;
import com.ruoyi.satisfaction.domain.vo.QuestionCountKsOrBqVO;
import com.ruoyi.satisfaction.domain.vo.SltdMydTotalVO;
import com.ruoyi.satisfaction.service.SatisfactionSubtaskService;
import com.ruoyi.satisfaction.service.strategy.SatisfactionScoreStrategyResolver;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 省立同德 / 门诊满意度统计 Controller(已迁入 ruoyi-satisfaction 模块)。
*
* ⚠️ URL 与出入参一字未改:这些端点原本挂在
* {@code ServiceSubtaskDetailController}({@code /smartor/serviceSubtaskDetail})下,
* 迁移后 {@code @RequestMapping} 与全部 {@code @PostMapping} 路径保持原样,前端无需任何改动。
*
* 迁移时唯一调整的一处:{@code sltdMydTotalByScore} 原本调用
* {@code serviceSubtaskService.getServiceSubtasks(...)}(随访主干 Service,不能搬),
* 现改为调用满意度模块自带的 {@link SatisfactionSubtaskService#getMydSubtaskRefs};
* 查询条件与后续统计口径逐字保持一致(见各方法内注释)。
*
* 开关:本 Controller 始终注册(不随 {@code satisfaction.enabled} 消失)。
* 模块被关闭时由 {@code SatisfactionAvailabilityInterceptor}
* (见 {@code config/SatisfactionWebConfig})在进入方法前抛出 {@code ServiceException},
* 调用方会拿到明确的错误提示(而不是 404)。
* 注意:同一前缀 {@code /smartor/serviceSubtaskDetail} 下的随访明细端点不受影响
* —— 拦截器只挂在本类的 5 个满意度路径上。
*/
@Api(description = "省立同德满意度统计")
@RestController
@RequestMapping("/smartor/serviceSubtaskDetail")
public class SltdSatisfactionController extends BaseController {
/**
* 站点口径策略解析器:综合满意度得分按站点取策略计算,
* 不再把算法与常量写在 Controller 里。
*
* 站点来自 {@code sys_config: satisfaction.site}(见 {@link SatisfactionSwitch#site()}):
* 同德用 {@code sltd}(五档文案加权),南华用 {@code common}(按明细 score 求平均)。
*/
@Autowired
private SatisfactionScoreStrategyResolver satisfactionScoreStrategyResolver;
/**
* 运行时配置(站点口径)。2026-10-21 新增:
* 原来这里把口径写死成 {@code SatisfactionSites.SLTD},南华的数据(没有"很满意/满意/…"五档文案)
* 一条选项都匹配不上 → 综合得分恒为 0.00。
*/
@Autowired
private SatisfactionSwitch satisfactionSwitch;
@Autowired
private SatisfactionSubtaskService satisfactionSubtaskService;
// ================================================================ 端点(URL 原样)
/**
* 省立同德满意度数据统计(按题目统计选项数量/占比,并给出该题的综合得分)
*
* 综合满意度 = (很满意项数×1 + 满意项数×0.8 + 一般项数×0.6 + 不满意项数×0.4 + 很不满意项数×0.2)
* / (很满意项数 + 满意项数 + 一般项数 + 不满意项数 + 很不满意项数) × 100%;
* 「未经历」「不适用」等不在五档里的选项既不进分子也不进分母。
*/
@ApiOperation("省立同德满意度数据统计")
@AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
@PostMapping("/sltdMydTotalByScore")
public Map sltdMydTotalByScore(@RequestBody SltdMydTotalVO sltdMydTotalVO) {
List subtaskRefs = null;
if (CollectionUtils.isNotEmpty(sltdMydTotalVO.getServiceTypeList())) {
//serviceType可能会出现多个,不想多写方法了,一个一个查询吧
for (String st : sltdMydTotalVO.getServiceTypeList()) {
SatisfactionSubtaskQuery query = new SatisfactionSubtaskQuery();
query.setServiceType(st);
query.setEndOutHospTime(sltdMydTotalVO.getEndOutHospTime());
query.setStartOutHospTime(sltdMydTotalVO.getStartOutHospTime());
//完成时间:页面提交的是 startFinishTime / endFinishTime。
//原来这两个条件没有向下传,取子任务的 SQL 完全没有时间条件
//→ 口径偏大(统计了全部历史)且 service_subtask 被整表扫描
query.setStartFinishTime(sltdMydTotalVO.getStartFinishTime());
query.setEndFinishTime(sltdMydTotalVO.getEndFinishTime());
query.setDeptOrDistrict(sltdMydTotalVO.getDeptOrDistrict());
//病区 / 科室:按「传进来的是哪一组参数」决定过滤哪一列,不再一律按离院病区过滤
// hospitaldistrictcodes → hospitaldistrictcode 在院/当前病区
// leavehospitaldistrictcodes → leavehospitaldistrictcode 离院病区
// deptcodes → deptcode 在院/当前科室
// leaveldeptcodes → leaveldeptcode 离院科室
//原来 hospitaldistrictcodes 这个字段在 VO 上根本不存在,被 Jackson 静默丢弃,
//导致病区条件从未拼进 SQL(查 40003514 病区却把骨科等其它病区的数据一起统计了)
query.setHospitaldistrictcodes(sltdMydTotalVO.getHospitaldistrictcodes());
query.setLeavehospitaldistrictcodes(sltdMydTotalVO.getLeavehospitaldistrictcodes());
query.setDeptcodes(sltdMydTotalVO.getDeptcodes());
query.setLeaveldeptcodes(sltdMydTotalVO.getLeaveldeptcodes());
//机构/院区由 @AddOrgId 注入,落到查询条件上,避免跨机构取数
query.setOrgid(sltdMydTotalVO.getOrgid());
query.setCampusid(sltdMydTotalVO.getCampusid());
List refs = satisfactionSubtaskService.getMydSubtaskRefs(query);
if (CollectionUtils.isNotEmpty(subtaskRefs)) {
subtaskRefs.addAll(refs);
} else {
if (CollectionUtils.isEmpty(subtaskRefs)) subtaskRefs = refs;
}
}
}
// 按 questiontext 分组后转换为 QuestionResultVO
List result = new ArrayList<>();
if (CollectionUtils.isNotEmpty(subtaskRefs)) {
List detailList =
satisfactionSubtaskService.getDetailRatio(sltdMydTotalVO, subtaskRefs);
if (detailList == null) {
//serviceType 组合不在支持范围内(如同时含随访 2 和满意度 6/14),给明确提示而不是悄悄返回空数组
return error("当前 serviceType 组合暂不支持统计问题占比,请分开查询(仅支持随访 2 或满意度 6/14)");
}
if (CollectionUtils.isNotEmpty(detailList)) {
Map> 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 statQuestionOptionByKsOrBq(@RequestBody QuestionCountKsOrBqVO questionResultVO) {
return success(satisfactionSubtaskService.statQuestionOptionByKsOrBq(questionResultVO));
}
@ApiOperation("省立同德患者满意度趋势")
@PostMapping("/mydTrend")
public Map mydTrend(@RequestBody MydTrendReqVO mydTrendReqVO) {
return success(satisfactionSubtaskService.mydTrend(mydTrendReqVO));
}
@ApiOperation("省立同德患者满意度维护")
@PostMapping("/sltdMydTotalByDimension")
public Map sltdMydTotalByDimension(@RequestBody MydTrendReqVO mydTrendReqVO) {
return success(satisfactionSubtaskService.sltdMydTotalByDimension(mydTrendReqVO));
}
@ApiOperation("门诊满意度综合得分排名(按科室或病区)")
@AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
@PostMapping("/mzMydScoreRank")
public Map mzMydScoreRank(@RequestBody MzMydScoreRankVO mzMydScoreRankVO) {
return success(satisfactionSubtaskService.mzMydScoreRank(mzMydScoreRankVO));
}
// ================================================================ 综评计算(改走站点策略)
/**
* 计算一道题的满意度综合得分(百分制,保留两位小数)。
*
* 策略化改造:算法本身已原样搬到
* {@link com.ruoyi.satisfaction.service.strategy.SltdSatisfactionScoreStrategy}
* (省立同德口径),本方法只负责"按站点取策略 + 把 null 呈现为 0.00"。
* 计算过程与返回值与迁移前完全一致。
*
* @param exportList 同一道题目的选项统计明细
* @return 百分制综合得分,无有效项数时返回 "0.00"
*/
private String calcMydScore(List exportList) {
BigDecimal score = satisfactionScoreStrategyResolver
.resolve(satisfactionSwitch.site())
.comprehensiveScore(exportList);
return score == null ? "0.00" : score.toPlainString();
}
}