liusheng
2026-10-12 b25c3b5188e402695f3792ad7da53e452682cd80
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
package com.ruoyi.satisfaction.mapper;
 
import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
import com.ruoyi.satisfaction.domain.dto.MydTrendReqDTO;
import com.ruoyi.satisfaction.domain.dto.MzMydScoreRankDTO;
import com.ruoyi.satisfaction.domain.dto.QuestionCountKsOrBqDTO;
import com.ruoyi.satisfaction.domain.dto.ServiceSubtaskOptionStatDTO;
import com.ruoyi.satisfaction.domain.entity.SatisfactionStatQuery;
import com.ruoyi.satisfaction.domain.row.SatisfactionSubtaskQuery;
import com.ruoyi.satisfaction.domain.row.SatisfactionSubtaskRef;
import com.ruoyi.satisfaction.domain.row.SatisfactionTaskRow;
import com.ruoyi.satisfaction.domain.vo.MzMydScoreRankVO;
import com.ruoyi.satisfaction.domain.vo.PatSatisfactionReqVO;
import com.ruoyi.satisfaction.domain.vo.QuestionCountKsOrBqVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
 
import java.util.Date;
import java.util.List;
import java.util.Map;
 
/**
 * 满意度统计 Mapper 接口。
 * <p>
 * 迁移说明:方法原本散落在 {@code com.smartor.mapper.ServiceSubtaskMapper} 与
 * {@code com.smartor.mapper.ServiceSubtaskDetailMapper} 中,满意度模块化后集中到此处。
 * <p>
 * 本模块自带 SQL,<b>直接查询</b> {@code service_subtask} / {@code service_subtask_detail},
 * 以及模板分类表;不依赖任何其它模块的 Mapper。
 * <p>
 * 对应 XML:{@code ruoyi-satisfaction/src/main/resources/mapper/satisfaction/SatisfactionMapper.xml}
 * <p>
 * 已删除的死代码(搬迁时确认零调用点):
 * <ul>
 *   <li>{@code selectSatisfactionSubtaskList} —— 被 {@code selectSatisfactionSubtaskTypes} 替代;</li>
 *   <li>{@code querySendCount} —— 被 {@code selectSatisfactionSendCount} 替代。</li>
 * </ul>
 */
@Mapper
public interface SatisfactionMapper {
 
    // ================================================================ 发送量统计
 
    /**
     * 满意度柱状图:一次性统计本次请求全部模板的发送量(按 libtemplateid 分组)。
     *
     * @param query 聚合查询条件
     * @return 每行含 libtemplateid、cnt
     */
    List<Map<String, Object>> selectSatisfactionSendCount(SatisfactionStatQuery query);
 
    /**
     * 满意度统计:只查本次条件下实际存在哪些 type(1 语音 / 2 问卷)。
     *
     * @param patSatisfactionReqVO 查询条件
     * @return 去重后的 type 列表(可能为 null)
     */
    List<String> selectSatisfactionSubtaskTypes(PatSatisfactionReqVO patSatisfactionReqVO);
 
    // ================================================================ 填报量统计
 
    /**
     * 满意度柱状图:一次性统计本次请求全部模板的填报量(按 模板+问题+score 分组)。
     *
     * @param query 聚合查询条件
     * @return 每行含 libtemplateid、questiontext、score、cnt
     */
    List<Map<String, Object>> selectSatisfactionFillStat(SatisfactionStatQuery query);
 
    /**
     * 满意度统计:一次性统计本次请求全部问题的填报量(按 模板+问题+matchedtext+score 分组)。
     *
     * @param query 聚合查询条件
     * @return 每行含 libtemplateid、questiontext、matchedtext、score、cnt、targetvalue
     */
    List<Map<String, Object>> selectSatisfactionFillStatByMatchedtext(SatisfactionStatQuery query);
 
    /**
     * 满意度柱状图:一次性统计本次请求全部模板的「回收人数」。
     * <p>
     * 回收人数必须是人数口径(count distinct sub_id),不能按题目汇总答案行数(题量口径)。
     *
     * @param query 聚合查询条件
     * @return 每行含 libtemplateid、cnt
     */
    List<Map<String, Object>> selectSatisfactionFillPersonCount(SatisfactionStatQuery query);
 
    /**
     * 【按 模板 + 问题】统计答题人数({@code count(distinct ssd.sub_id)})。
     * <p>
     * <b>为什么单独加这一条</b>:{@code /statistics} 的「答题人数 / 未答题人数」必须与
     * 「发送人数」({@link #selectSatisfactionSendCount},也是人数)同口径。
     * 原实现误用 {@link #selectSatisfactionFillStatByMatchedtext} 的 {@code count(*)}(明细<b>行数</b>),
     * 而一条 {@code (sub_id, questiontext)} 可能有多行明细(多选项 / 重复明细),
     * 导致「未答题人数 = 发送人数 − 明细行数」出现负数(实测 -120)。
     * <p>
     * ⚠️ 平均分 / 最高分 / 最低分 / 各选项占比仍使用明细行数(那些口径本来就按行计),未改动。
     * <p>
     * 除聚合键与 {@code distinct} 外,from/where 与 {@link #selectSatisfactionFillStatByMatchedtext}
     * 完全一致,保证两者统计的是同一批数据。
     *
     * @param query 聚合查询条件
     * @return 每行含 libtemplateid、questiontext、cnt(人数)
     */
    List<Map<String, Object>> selectSatisfactionFillPersonCountByQuestion(SatisfactionStatQuery query);
 
    // ================================================================ 省立同德:趋势与维度
 
    /**
     * 省立同德患者满意度趋势。
     *
     * @param startFinishTime 开始时间
     * @param endFinishTime   结束时间
     * @param type            粒度:week / month / year / day
     * @return 趋势数据
     */
    List<MydTrendReqDTO> mydTrend(@Param("startFinishTime") Date startFinishTime,
                                  @Param("endFinishTime") Date endFinishTime,
                                  @Param("type") String type);
 
    /**
     * 省立同德患者满意度——按科室维度统计。
     */
    List<MydTrendReqDTO> sltdMydTotalByDimension(@Param("startFinishTime") Date startFinishTime,
                                                 @Param("endFinishTime") Date endFinishTime,
                                                 @Param("type") String type);
 
    /**
     * 省立同德患者满意度——按院区维度统计。
     */
    List<MydTrendReqDTO> sltdMydTotalByYQ(@Param("startFinishTime") Date startFinishTime,
                                          @Param("endFinishTime") Date endFinishTime,
                                          @Param("type") String type);
 
    // ================================================================ 选项占比
 
    /**
     * 统计满意度选项的百分比。
     *
     * @param taskIds         任务 ID 列表
     * @param subtaskIds      子任务 ID 列表(非空时只统计这些子任务;为空则统计这些任务下的全部子任务)
     * @param startOutHospTime 出院开始时间
     * @param endOutHospTime   出院结束时间
     * @param startFinishTime  完成开始时间
     * @param endFinishTime    完成结束时间
     * @param scoreStart       分值下限
     * @param scoreEnd         分值上限
     * @param questiontext     题目内容(模糊匹配)
     * @param valueType        题型(2=多选)
     */
    List<ServiceSubtaskDetailRatioExport> statQuestionOption(
            @Param("taskIds") List<Long> taskIds,
            @Param("startOutHospTime") Date startOutHospTime,
            @Param("endOutHospTime") Date endOutHospTime,
            @Param("startFinishTime") Date startFinishTime,
            @Param("endFinishTime") Date endFinishTime,
            @Param("scoreStart") Double scoreStart,
            @Param("scoreEnd") Double scoreEnd,
            @Param("questiontext") String questiontext,
            @Param("valueType") String valueType,
            @Param("subtaskIds") List<Long> subtaskIds);
 
    /**
     * 满意度导出:选项占比。
     * <p>
     * 项数 = 选了该选项的患者数(同一人同一题只取最新一条明细);分数 = 该选项所有明细 score 之和。
     *
     * @param subtaskIds   本次导出的子任务 ID
     * @param questiontext 题目内容(可选,模糊匹配)
     */
    List<ServiceSubtaskOptionStatDTO> statMydQuestionOptionForExport(
            @Param("subtaskIds") List<Long> subtaskIds,
            @Param("questiontext") String questiontext);
 
    /**
     * 根据子任务 ID 统计满意度选项的百分比(用于满意度统计 serviceType=6/14)。
     */
    List<ServiceSubtaskDetailRatioExport> statQuestionOptionBySubtaskIds(
            @Param("subtaskIds") List<Long> subtaskIds,
            @Param("startOutHospTime") Date startOutHospTime,
            @Param("endOutHospTime") Date endOutHospTime,
            @Param("startFinishTime") Date startFinishTime,
            @Param("endFinishTime") Date endFinishTime,
            @Param("scoreStart") Double scoreStart,
            @Param("scoreEnd") Double scoreEnd,
            @Param("questiontext") String questiontext,
            @Param("valueType") String valueType);
 
    /**
     * 按科室/病区对满意度题目做归类统计。
     */
    List<QuestionCountKsOrBqDTO> statQuestionOptionByKsOrBq(QuestionCountKsOrBqVO questionCountKsOrBqVO);
 
    // ================================================================ 综合得分排名
 
    /**
     * 门诊满意度综合得分排名(按科室或病区)。
     * <p>
     * 综合满意度 = Σ(评分明细权重) / 评分明细条数 × 100%;权重只取 1、0.8、0.6、0.4、0.2 五档。
     *
     * @param mzMydScoreRankVO 查询条件(dimensionType、serviceType、时间区间、科室/病区编号、orgid/campusid)
     * @return 每行科室/病区的综合得分,已按综合得分降序
     */
    List<MzMydScoreRankDTO> selectMzMydScoreRank(MzMydScoreRankVO mzMydScoreRankVO);
    // ================================================================ 重复明细清理(满意度统计前置)
 
    /**
     * 找出「同一 sub_id + questiontext」下除最新一条之外的重复明细 id。
     * <p>
     * 原属 {@code ServiceSubtaskDetailMapper},只有满意度统计调用(已核实),故一并搬入。
     * <p>
     * ⚠️ 注意:调用它的 {@code statQuestionOption} / {@code statQuestionOptionBySubtaskIds}
     * 会先执行本查询再执行 {@link #deleteRepeatDetailByIds},<b>这是会写库的行为</b>(原实现即如此)。
     *
     * @return 需要清理的明细 id
     */
    List<Long> selectRepeatDetailIds();
 
    /**
     * 按 id 逻辑删除重复明细({@code del_flag = 1})。
     */
    Integer deleteRepeatDetailByIds(@Param("ids") List<Long> ids);
    // ================================================================ 子任务(满意度统计取数)
 
    /**
     * 按机构/院区/服务类型/出院时间/科室病区取子任务,<b>只返回 id 与 taskid</b>。
     * <p>
     * 对应原实现 {@code ServiceSubtaskServiceImpl.getServiceSubtasks(ServiceSubtaskEntity)}
     * 中 {@code sltdMydTotalByScore} 实际用到的口径。原方法基于
     * {@code selectServiceSubtaskList}(近百个可选条件 + 返回全表字段),
     * 满意度只需要上述条件与两个 id 列,因此这里专写一条精简查询。
     * <p>
     * <b>条件与原文逐字一致</b>(均「非空才拼」):
     * {@code del_flag = 0}、{@code orgid}、{@code campusid}、{@code service_type}、
     * {@code endtime} 的日期区间、{@code deptOrDistrict==1} 时的病区 IN、
     * {@code leaveldeptcodes} 的科室 IN。
     * <p>
     * ⚠️ 原文无默认排序、无默认分页({@code sort}/{@code pageSize} 未设置即不生效),
     * 本查询同样不加排序,保证结果顺序行为一致。
     */
    List<SatisfactionSubtaskRef> selectMydSubtaskRefs(@Param("q") SatisfactionSubtaskQuery query);
 
    // ================================================================ 随访「满意度指数」取数
    // 以下 5 个方法服务于 getSatisfactionIndex(随访统计页的「满意度题目总量/填报量/完成比例」),
    // 原本散落在 smartor 的 ServiceTaskMapper / IvrTaskTemplateScriptMapper /
    // SvyTaskTemplateScriptMapper / ServiceSubtaskMapper / ServiceSubtaskDetailMapper 中,
    // 由 ServiceSubtaskServiceImpl#getSatisfactionIndex 逐个调用。
 
    /**
     * 按科室 <b>或</b> 病区取随访任务(只取统计用到的 4 列)。
     * <p>
     * 对应原实现 {@code ServiceTaskMapper.selectServiceTaskList},调用方按 type 二选一传参:
     * <ul>
     *   <li>{@code deptcode} 非空 → 拼 {@code deptcode REGEXP CONCAT('(^|,)', #{deptcode}, '(,|$)')};</li>
     *   <li>{@code leavehospitaldistrictcode} 非空 → 拼同形态的 REGEXP;</li>
     * </ul>
     * 两者只会有一个非空,因此本方法一条 SQL 即可覆盖原实现的两个分支。
     * <p>
     * <b>口径逐字一致</b>:{@code del_flag = 0}、{@code orgid}、{@code order by update_time desc, taskid desc}。
     */
    List<SatisfactionTaskRow> selectSatisfactionServiceTasks(
            @Param("orgid") String orgid,
            @Param("deptcode") String deptcode,
            @Param("leavehospitaldistrictcode") String leavehospitaldistrictcode);
 
    /**
     * 统计某模板下的语音满意度题目数量。
     * <p>
     * 对应原实现 {@code IvrTaskTemplateScriptMapper.selectIvrTaskTemplateScriptList}
     * 在 {@code templateID + orgid + scriptAssortid} 三个条件下的 {@code size()}。
     * <p>
     * <b>口径注意</b>:{@code scriptAssortid} 为 {@code null} 时<b>不拼</b>该条件
     * (等于统计该模板全部题目)——与原文 {@code <if test="scriptAssortid != null">} 一致。
     */
    int countIvrTaskTemplateScripts(@Param("templateID") Long templateID,
                                    @Param("orgid") String orgid,
                                    @Param("scriptAssortid") Long scriptAssortid);
 
    /**
     * 统计某模板下的问卷满意度题目数量。
     * <p>
     * 对应原实现 {@code SvyTaskTemplateScriptMapper.selectSvyTaskTemplateScriptList}
     * 在 {@code templateID + orgid + categoryid} 三个条件下的 {@code size()}。
     * <p>
     * <b>口径注意</b>:{@code categoryid} 为 {@code null} 时<b>不拼</b>该条件(与原文一致)。
     */
    int countSvyTaskTemplateScripts(@Param("templateID") Long templateID,
                                    @Param("orgid") String orgid,
                                    @Param("categoryid") Long categoryid);
 
    /**
     * 统计某任务下指定发送状态的子任务数量。
     * <p>
     * 对应原实现 {@code ServiceSubtaskMapper.selectServiceSubtaskBySendstate}
     * 在 {@code taskid + sendstates(3,5,6)} 两个条件下的 {@code size()}。
     * 原调用没有设置 {@code orgid},故本方法也不含该条件。
     * <p>
     * <b>口径注意</b>:含 {@code del_flag = 0};{@code sendstates} 为 {@code null} 时不拼 IN 条件。
     */
    int countSubtaskBySendstates(@Param("taskid") Long taskid,
                                @Param("sendstates") List<Long> sendstates);
 
    /**
     * 统计某任务下的满意度明细数量。
     * <p>
     * 对应原实现 {@code ServiceSubtaskDetailMapper.selectServiceSubtaskDetailList}
     * 在 {@code taskid + categoryid + orgid} 三个条件下的 {@code size()}。
     * <p>
     * <b>口径注意</b>:原文<b>没有</b> {@code del_flag} 条件,本查询同样不加;
     * {@code categoryid} 为 {@code null} 时不拼该条件(保留原文的"按任务类型决定是否设 categoryid"行为)。
     */
    int countSubtaskDetail(@Param("taskid") Long taskid,
                           @Param("categoryid") Long categoryid,
                           @Param("orgid") String orgid);
}