liusheng
4 天以前 0bbbd746e8c39d91ad7b47a4e36e29ab972dc98c
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
package com.ruoyi.inhospvisit.controller;
 
import com.github.pagehelper.ISelect;
import com.github.pagehelper.PageInfo;
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.page.TableDataInfo;
import com.ruoyi.common.core.service.dto.PatMedReq;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.PageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.inhospvisit.api.InhospVisitFacade;
import com.ruoyi.inhospvisit.domain.PatMedInhosp;
import com.ruoyi.inhospvisit.domain.PatMedInhospDTO;
import com.ruoyi.inhospvisit.domain.PatMedInhospVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
 
import javax.servlet.http.HttpServletResponse;
import java.util.List;
 
/**
 * 基础出入院信息<b>查询 / CRUD 端点</b> —— 从 {@code ruoyi-admin} 的
 * {@code PatMedInhospController} 迁入("其它模块只能从本模块调用出入院信息"的落地点之一)。
 *
 * <h3>⚠️ URL 一个都不能变</h3>
 * 类级路径 {@code /smartor/patinhosp} 与原 Controller 逐字一致(本类与
 * {@link InhospVisitController} 共用该前缀,但<b>方法级路径完全不重叠</b>,
 * 因此不会 {@code Ambiguous mapping});11 个方法级路径也逐字照抄。
 * 真实前端在仓库外({@code D:\daima\smartor\smartor-web}),改了地址就无法回归。
 * 由 {@code sql/_probe/verify_inhospvisit_url_equivalence.py} 把关。
 *
 * <h3>数据访问路径</h3>
 * 本类<b>不注入任何 smartor 的 Service</b>,一律走本模块门面
 * {@link InhospVisitFacade}(它实现了 {@code InhospVisitDataService})。
 * 这也正是本次改造要达成的事实:出入院信息只有本模块一处实现。
 *
 * <h3>哪 4 个端点<b>没有</b>迁进来(及其原因)</h3>
 * <ul>
 *   <li>{@code /getDeptRanking} —— SQL 在 {@code ServiceSubtaskMapper.getDeptRanking},
 *       属子任务域而非出入院信息;</li>
 *   <li>{@code /selectPatMedInhospByApplication} —— 依赖 {@code ServiceSubtaskMapper}
 *       与 {@code PatMedInhospDTO} 转换;<b>且原实现有反向逻辑缺陷</b>(见 10.4.10 注释);</li>
 *   <li>{@code /getDocAndPat} —— 要合并 {@code PatArchiveImport}(档案导入域)的数据;</li>
 *   <li>{@code /getDeptCodeByPatId} —— 依赖 {@code PatMedInhospDTO} 转换链。</li>
 * </ul>
 * 这 4 个仍由 {@code com.ruoyi.web.controller.smartor.PatMedInhospController} 承载,URL 不变。
 *
 * <h3>开关如何生效</h3>
 * 本模块的 {@code InhospVisitAvailabilityInterceptor} 只挂在<b>随访触发的精确路径</b>上
 * ({@code /dealOutHospInfo}、{@code /dealOutHospInfoBySchedule}),
 * <b>不含本类的任何数据端点</b> —— 与迁入前的行为完全一致:
 * 关闭 {@code inhospvisit.enabled} 只停"随访生成",不会让出入院记录的查询/CRUD 不可用。
 * 依据是 {@code sys_config: inhospvisit.enabled} 的本意(关闭 = 当天不再生成随访),
 * 而不是"关闭整个模块"。这一点在 {@code InhospVisitWebConfig} 里有对应注释。
 */
@Slf4j
@Api(description = "基础出入院信息")
@RestController
@RequestMapping("/smartor/patinhosp")
public class InhospVisitDataController extends BaseController {
 
    /**
     * 本模块常驻门面(同时实现随访契约与数据契约)。
     * <p>
     * ⚠️ 注入门面而不是 {@code PatMedInhospMapper}:门面永远存在,
     * 不会因开关状态或装配差异导致本 Controller 启动失败。
     */
    @Autowired
    private InhospVisitFacade inhospVisitFacade;
 
    /**
     * 查询患者住院记录列表。
     * <p>
     * 原 {@code PatMedInhospController#selectPatMedInhosplist}(L77-87)逐行对应,
     * 唯一多出来的一行是 {@link #normalizeBedSort} —— 见该方法注释:
     * 原实现里这段归一化在 {@code PatMedInhospServiceImpl} 里,迁到本模块后这条链
     * 直接调门面,必须把它跟着搬过来,否则 {@code bedSort} 静默失效。
     */
    @ApiOperation("查询患者住院记录列表")
    //@PreAuthorize("@ss.hasPermi('smartor:patinhosp:list')")
    @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
    @PostMapping("/selectPatMedInhospList")
    public TableDataInfo selectPatMedInhosplist(@RequestBody PatMedInhosp patMedInhosp) {
        PageUtils.startPageByPost(patMedInhosp.getPageNum(), patMedInhosp.getPageSize());
        SysUser user = getLoginUser().getUser();
        patMedInhosp.setOrgid(user.getOrgid());
        // 前端传的是数字 1 / 2,SQL 只认 "asc" / "desc";不在这里归一就等于没传
        patMedInhosp.setBedSort(normalizeBedSort(patMedInhosp.getCry(), patMedInhosp.getBedSort()));
        List<PatMedInhosp> list = inhospVisitFacade.selectPatMedInhospList(patMedInhosp);
        // startPage 之后 PageHelper 已经自动执行过一次 count 查询,直接复用 Page 里的 total,
        // 避免用 PageUtils.count 把带 JOIN 的统计语句再跑一遍
        long count = new PageInfo<>(list).getTotal();
        return getDataTable2(count, list);
    }
 
    /**
     * 查询当前登陆人自己(病区、部门)的患者住院记录列表。
     * <p>
     * 原 {@code PatMedInhospController#selectPatMedInhospListByCondition}(L126-142)逐行对应。
     * <p>
     * ⚠️ 原实现里两个分支调用的是<b>同一个方法</b>(只是条件不同),此处原样保留 ——
     * 合并分支会改变"两个条件都为空时 list 保持 null"这一既有行为。
     */
    @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
    @ApiOperation("查询当前登陆人自己(病区、部门)的患者住院记录列表")
    @PostMapping("/selectPatMedInhospListByCondition")
    public TableDataInfo selectPatMedInhospListByCondition(@RequestBody PatMedInhospVO patMedInhospVO) {
        PageUtils.startPageByPost(patMedInhospVO.getPageNum(), patMedInhospVO.getPageSize());
        List<PatMedInhosp> list = null;
        if (!CollectionUtils.isEmpty(patMedInhospVO.getLeavehospitaldistrictcodes())) {
            list = inhospVisitFacade.selectPatMedInhospListByCondition(patMedInhospVO);
        } else if (!CollectionUtils.isEmpty(patMedInhospVO.getLeaveldeptcodes())) {
            list = inhospVisitFacade.selectPatMedInhospListByCondition(patMedInhospVO);
        }
        long count = PageUtils.count(new ISelect() {
            @Override
            public void doSelect() {
                inhospVisitFacade.selectPatMedInhospListByCondition(patMedInhospVO);
            }
        });
        return getDataTable2(count, list);
    }
 
    /**
     * 查询患者住院记录通过入院申请单信息。
     * <p>
     * 原 {@code PatMedInhospController#selectPatMedInhospByApplication} 逐行对应
     * (含分页、{@code @AddOrgId}、以及 count 用的 {@code ISelect})。
     * <p>
     * ⚠️ <b>本端点原有的反向判断缺陷被原样保留</b>(见 {@code InhospVisitDataService}
     * 对该方法的说明):查到记录时服务层返回 {@code null},所以本端点实际恒返回空数据。
     * 前端当前没有调用它(已核对 {@code smartor-web/src})。
     * 按"原实现怪癖一律原样保留、只在注释标注"的纪律,本步<b>不修</b>它。
     */
    @ApiOperation("查询患者住院记录通过入院申请单信息")
    //@PreAuthorize("@ss.hasPermi('smartor:patinhosp:list')")
    @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
    @PostMapping("/selectPatMedInhospByApplication")
    public TableDataInfo selectPatMedInhospByApplication(@RequestBody PatMedInhosp patMedInhosp) {
        PageUtils.startPageByPost(patMedInhosp.getPageNum(), patMedInhosp.getPageSize());
        SysUser user = getLoginUser().getUser();
        patMedInhosp.setOrgid(user.getOrgid());
        List<PatMedInhospDTO> list = inhospVisitFacade.selectPatMedInhospByApplication(patMedInhosp);
        long count = PageUtils.count(new ISelect() {
            @Override
            public void doSelect() {
                inhospVisitFacade.selectPatMedInhospByApplication(patMedInhosp);
            }
        });
        return getDataTable2(count, list);
    }
 
    /**
     * 查询出、入院看病人次和人数(人数分为首次服务、再次服务、专病服务)。
     * <p>
     * 原 {@code PatMedInhospController#selectPatMedInhospListCount}(L153-160)逐行对应。
     * 前端首页 ECharts 在用({@code src/api/AiCentre/EChartsdata.js}),地址不可变。
     */
    @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
    @PostMapping("/selectPatMedInhospListCount")
    @ApiOperation("查询出、入院看病人次和人数")
    public AjaxResult selectPatMedInhospListCount(@RequestBody PatMedReq patMedReq) {
        SysUser user = getLoginUser().getUser();
        patMedReq.setOrgid(user.getOrgid());
        return success(inhospVisitFacade.selectPatMedInhospCount(patMedReq));
    }
 
    /**
     * 导出患者住院记录列表。
     * <p>
     * 原 {@code PatMedInhospController#export}(L165-173)逐行对应。
     * <p>
     * {@code bedSort} 也要归一:迁出前的 export 同样走
     * {@code patMedInhospService.selectPatMedInhospList(...)}(见 {@code ccf5fc76} 版本),
     * 归一化是随那次调用生效的。不补这一行就会出现"列表按床号排好了、导出顺序又变回去"。
     */
    @ApiOperation("导出患者住院记录列表")
    //@PreAuthorize("@ss.hasPermi('smartor:patinhosp:export')")
    @Log(title = "患者住院记录", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, PatMedInhosp patMedInhosp) {
        patMedInhosp.setBedSort(normalizeBedSort(patMedInhosp.getCry(), patMedInhosp.getBedSort()));
        List<PatMedInhosp> list = inhospVisitFacade.selectPatMedInhospList(patMedInhosp);
        ExcelUtil<PatMedInhosp> util = new ExcelUtil<PatMedInhosp>(PatMedInhosp.class);
        util.exportExcel(response, list, "患者住院记录数据");
    }
 
    /**
     * 获取患者住院记录详细信息。
     * <p>
     * 原 {@code PatMedInhospController#getInfo}(L178-183)逐行对应。
     */
    @ApiOperation("获取患者住院记录详细信息")
    //@PreAuthorize("@ss.hasPermi('smartor:patinhosp:query')")
    @GetMapping(value = "/getInfo/{inhospid}")
    public AjaxResult getInfo(@PathVariable("inhospid") Long inhospid) {
        return success(inhospVisitFacade.selectPatMedInhospByInhospid(inhospid));
    }
 
    /**
     * 新增患者住院记录。
     * <p>
     * 原 {@code PatMedInhospController#add}(L188-197)逐行对应。
     */
    @ApiOperation("新增患者住院记录")
    //@PreAuthorize("@ss.hasPermi('smartor:patinhosp:add')")
    @Log(title = "患者住院记录", businessType = BusinessType.INSERT)
    @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
    @PostMapping("/add")
    public AjaxResult add(@RequestBody PatMedInhosp patMedInhosp) {
        SysUser user = getLoginUser().getUser();
        patMedInhosp.setOrgid(user.getOrgid());
        return toAjax(inhospVisitFacade.insertPatMedInhosp(patMedInhosp));
    }
 
    /**
     * 修改患者住院记录。
     * <p>
     * 原 {@code PatMedInhospController#edit}(L202-209)逐行对应。
     */
    @ApiOperation("修改患者住院记录")
    //@PreAuthorize("@ss.hasPermi('smartor:patinhosp:edit')")
    @Log(title = "患者住院记录", businessType = BusinessType.UPDATE)
    @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
    @PostMapping("/edit")
    public AjaxResult edit(@RequestBody PatMedInhosp patMedInhosp) {
        return toAjax(inhospVisitFacade.updatePatMedInhosp(patMedInhosp));
    }
 
    /**
     * 删除患者住院记录。
     * <p>
     * 原 {@code PatMedInhospController#remove}(L214-220)逐行对应。
     */
    @ApiOperation("删除患者住院记录")
    //@PreAuthorize("@ss.hasPermi('smartor:patinhosp:remove')")
    @Log(title = "患者住院记录", businessType = BusinessType.DELETE)
    @GetMapping("/remove/{inhospids}")
    public AjaxResult remove(@PathVariable Long[] inhospids) {
        return toAjax(inhospVisitFacade.deletePatMedInhospByInhospids(inhospids));
    }
 
    /**
     * 归一化"入院床位排序"参数。
     * <p>
     * 行为与 <b>{@code 接口文档/PatMedInhosp 患者住院记录接口文档.md} 里 bedSort 的说明</b>
     * 以及迁出前的 {@code PatMedInhospServiceImpl#normalizeBedSort}(smartor,L213-219)
     * 完全一致:
     * <pre>
     *   asc  / 1 -> "asc"   床位正序
     *   desc / 2 -> "desc"  床位倒序
     *   其余(含不传、空串、非法值)-> null  不排序,保持原有默认排序
     *   只对「在院」查询(cry=0)生效;出院 / 转科 / 预入院 一律忽略该参数
     * </pre>
     * <p>
     * ⚠️ <b>为什么必须在 Java 侧归一,而不是让 SQL 也认 "1"/"2"</b>:
     * <ul>
     *   <li>前端传的是 <b>数字</b> {@code bedSort:1},Jackson 落到 {@code String} 字段是
     *       {@code "1"},而 mapper 的 {@code <choose>} 只匹配字符串 {@code "asc"/"desc"}
     *       —— 不归一就静默落回默认排序,"按床位排序"这个功能等于没生效(真实线上缺陷);</li>
     *   <li>同一个排序契约还出现在 {@code VisitInhospMapper.xml}(随访扫描那条链,
     *       入参是 {@code VisitInhospRow}),把"只认 asc/desc"留在 SQL 里,
     *       两条链对外的约定才是一致的;</li>
     *   <li>迁到本模块后这条链<b>不再经过</b> {@code PatMedInhospServiceImpl},
     *       原归一化会随迁出一起丢掉,所以搬到这里补回来。</li>
     * </ul>
     * 由 {@code sql/_probe/verify_patinhosp_bedsort.py} 把关。
     *
     * @param cry     出入院标识:0 入院(在院) 1 出院 2 转科 3 预入院
     * @param bedSort 前端传入的床位排序值("asc"/"1"/"desc"/"2"/空)
     * @return {@code "asc"} / {@code "desc"} / {@code null}(不排序)
     */
    private static String normalizeBedSort(Integer cry, String bedSort) {
        // 只对「在院」查询生效:cry != 0 一律置空,保证出院等场景排序不变
        if (cry == null || cry != 0 || StringUtils.isEmpty(bedSort)) {
            return null;
        }
        String value = bedSort.trim().toLowerCase();
        if ("asc".equals(value) || "1".equals(value)) {
            return "asc";
        }
        if ("desc".equals(value) || "2".equals(value)) {
            return "desc";
        }
        // 非法值:不排序(与"不传"同义),避免落到 XML 的 <otherwise> 之外的行为
        return null;
    }
}