liusheng
5 天以前 cc479e6a056785103996cc7ea67dbd5faa3e800e
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
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
package com.ruoyi.inhospvisit.api;
 
import com.ruoyi.inhospvisit.config.InhospVisitProperties;
import com.ruoyi.inhospvisit.config.InhospVisitSwitch;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
/**
 * 出入院随访模块对外门面(<b>常驻 Bean</b>)。
 * <p>
 * 这是其它模块访问出入院随访能力的<b>唯一入口</b>。
 *
 * <h3>与内部实现的关系(照抄满意度模块,5 个坑全部规避)</h3>
 * <ul>
 *   <li>实现类 {@link DefaultInhospVisitApi} <b>常驻</b>(不用 {@code @ConditionalOnProperty}
 *       —— 那个只在启动时求值一次,与"改完参数即时生效"冲突);</li>
 *   <li>开关在 {@code sys_config} 的 {@code inhospvisit.enabled},由
 *       {@link InhospVisitSwitch} <b>运行时</b>读取(10 秒进程内缓存),改完无需重启;</li>
 *   <li>本门面<b>永远</b>是 Spring Bean,因此调用方({@code RyTask} 定时任务、
 *       {@code PatMedInhospController})的 {@code @Autowired} 在任何开关状态下都<b>不会启动失败</b>;</li>
 *   <li>关闭时各方法返回安全空值并打 WARN,<b>不抛异常</b>,保证调用方主流程照常完成。</li>
 * </ul>
 *
 * <h3>⚠️ 本门面同时实现三个接口</h3>
 * <ul>
 *   <li>{@link InhospVisitApi} —— 本模块的<b>随访业务能力契约</b>(smartor / ruoyi-quartz 用),
 *       受 {@code inhospvisit.enabled} 开关管辖;</li>
 *   <li>{@link InhospVisitDataService} —— <b>基础出入院信息读写契约</b>(本次新增)。
 *       {@code PatMedInhospMapper} 已独占搬入本模块,其它模块一律经此访问;
 *       <b>刻意不受开关管辖</b>,否则关闭随访会连带让 HIS 采集瘫痪;</li>
 *   <li>{@code com.ruoyi.common.core.service.PublicitySubtaskApi} —— <b>下沉给宣教模块的最小面</b>。
 *       因为 ruoyi-publicity 不能依赖本模块(否则双向依赖),只能按 ruoyi-common 里的接口查找。</li>
 * </ul>
 *
 * <h3>使用示例</h3>
 * <pre>
 * &#64;Autowired
 * private InhospVisitFacade inhospVisitFacade;
 *
 * if (inhospVisitFacade.isEnabled()) {
 *     inhospVisitFacade.dealOutHospInfo(config);
 * }
 *
 * // 基础出入院信息:不需要判开关
 * List&lt;PatMedInhosp&gt; rows = inhospVisitFacade.selectPatMedInhospList(query);
 * </pre>
 */
@Slf4j
@Component
public class InhospVisitFacade implements InhospVisitApi,
        InhospVisitDataService,
        com.ruoyi.common.core.service.PreachformApi,
        com.ruoyi.common.core.service.PublicitySubtaskApi {
 
    private final InhospVisitProperties properties;
 
    /**
     * 宣教单({@code service_subtask_preachform})数据访问。
     * <p>
     * ⚠️ 用 {@code ObjectProvider} 惰性取,而不是直接注入接口 —— 理由同 {@code dataService}:
     * 本类自身也实现了 {@code PreachformApi},按接口类型直接注入会命中"正在创建的自己"
     * ({@code BeanCurrentlyInCreationException})。
     * <p>
     * 该表的归属模块是本模块;跨模块 SQL 收口后,外部只经 {@code PreachformApi} 访问它
     * (此前 smartor 里另有一整份同表 Mapper 与 XML,已删除)。
     */
    private final ObjectProvider<com.ruoyi.common.core.service.PreachformApi> preachformApiProvider;
 
    /**
     * 运行时开关({@code sys_config} 优先,{@link InhospVisitProperties} 兜底)
     */
    private final InhospVisitSwitch inhospVisitSwitch;
 
    /**
     * 内部实现;模块关闭时为 {@code null}。
     * <p>
     * 用 {@code ObjectProvider} 惰性获取:即使实现 Bean 不存在也不会导致注入失败。
     */
    private final ObjectProvider<InhospVisitApi> apiProvider;
 
    /**
     * 基础出入院信息数据访问(<b>必存在</b>,不受开关管辖)。
     * <p>
     * ⚠️ <b>用 {@link ObjectProvider} 惰性获取,并在解析时过滤掉"自己"</b>:
     * 本类自身也实现了 {@link InhospVisitDataService},若直接注入接口类型,
     * Spring 解析候选时会命中<b>正在创建的自己</b>,抛
     * {@code BeanCurrentlyInCreationException}(实测确认)。
     * 详见 {@link #firstDataServiceOrNull(ObjectProvider)}。
     * <p>
     * 用 {@code ObjectProvider} 而非直接注入还带来装配健壮性:容器里没有该实现
     * (例如不起数据库的测试上下文)时,门面依然能装配,数据方法走降级分支,
     * 而不会让整个应用启动失败。
     */
    private final InhospVisitDataService dataService;
 
    /**
     * Spring 使用的构造器。
     * <p>
     * ⚠️ <b>必须显式标注 {@code @Autowired}</b>:本类有两个公开构造器
     * (另一个是给测试/非 Spring 环境用的),若不标注,Spring 会因为"构造器不唯一"
     * 而退回查找无参构造器,从而在启动时报 {@code No default constructor found}。
     * —— 这是满意度模块<b>实机踩到</b>的缺陷,此处刻意保留同样的写法与注释。
     */
    @Autowired
    public InhospVisitFacade(InhospVisitProperties properties,
                             InhospVisitSwitch inhospVisitSwitch,
                             ObjectProvider<InhospVisitApi> apiProvider,
                             ObjectProvider<InhospVisitDataService> dataServiceProvider,
                             ObjectProvider<com.ruoyi.common.core.service.PreachformApi> preachformApiProvider) {
        this.properties = properties;
        this.inhospVisitSwitch = inhospVisitSwitch;
        this.apiProvider = apiProvider;
        this.dataService = firstDataServiceOrNull(dataServiceProvider);
        this.preachformApiProvider = preachformApiProvider;
    }
 
    /**
     * 取宣教单数据访问实现;容器里没有、状态异常、或<b>只找到自己</b>时返回 {@code null}。
     * <p>
     * ⚠️ 与 {@link #firstDataServiceOrNull} 同一手法与理由(自引用 / 循环依赖)。
     */
    private com.ruoyi.common.core.service.PreachformApi preachformOrNull() {
        if (preachformApiProvider == null) {
            return null;
        }
        try {
            return preachformApiProvider.stream()
                    .filter(svc -> svc != this)
                    .findFirst()
                    .orElse(null);
        } catch (Exception e) {
            log.warn("【出入院随访】获取宣教单数据服务失败,相关访问将降级:{}", e.toString());
            return null;
        }
    }
 
    /**
     * 数据服务是否不可用(不可用时打 WARN 并返回 true)
     */
    private boolean preachformUnavailable(String method) {
        if (preachformOrNull() == null) {
            log.warn("【出入院随访】宣教单数据服务不可用,{} 跳过。", method);
            return true;
        }
        return false;
    }
 
    // ==================================================================
    // 宣教单 service_subtask_preachform(PreachformApi)
    //
    // ⚠️ 与上面 6 个随访方法的关键区别:以下方法【不做开关判断】。
    //    它是跨模块 SQL 收口后的数据访问入口(替代 smartor 里那份同表 Mapper),
    //    属数据基础设施;开关只该控制"要不要生成随访"。
    // ==================================================================
 
    @Override
    public com.ruoyi.common.core.service.dto.VisitPreachformRow findById(Long id) {
        return preachformUnavailable("findById") ? null : preachformOrNull().findById(id);
    }
 
    @Override
    public java.util.List<com.ruoyi.common.core.service.dto.VisitPreachformRow> findList(
            com.ruoyi.common.core.service.dto.VisitPreachformRow query) {
        return preachformUnavailable("findList")
                ? java.util.Collections.<com.ruoyi.common.core.service.dto.VisitPreachformRow>emptyList()
                : preachformOrNull().findList(query);
    }
 
    @Override
    public java.util.List<com.ruoyi.common.core.service.dto.VisitPreachformRow> findByTaskidAndOrgid(
            Long taskid, String orgid) {
        return preachformUnavailable("findByTaskidAndOrgid")
                ? java.util.Collections.<com.ruoyi.common.core.service.dto.VisitPreachformRow>emptyList()
                : preachformOrNull().findByTaskidAndOrgid(taskid, orgid);
    }
 
    @Override
    public boolean insert(com.ruoyi.common.core.service.dto.VisitPreachformRow row) {
        return !preachformUnavailable("insert") && preachformOrNull().insert(row);
    }
 
    @Override
    public boolean updateById(com.ruoyi.common.core.service.dto.VisitPreachformRow row) {
        return !preachformUnavailable("updateById") && preachformOrNull().updateById(row);
    }
 
    @Override
    public boolean updateByCondition(com.ruoyi.common.core.service.dto.VisitPreachformRow row) {
        return !preachformUnavailable("updateByCondition") && preachformOrNull().updateByCondition(row);
    }
 
    @Override
    public boolean softDeleteById(Long id) {
        return !preachformUnavailable("softDeleteById") && preachformOrNull().softDeleteById(id);
    }
 
    @Override
    public boolean softDeleteByIds(Long[] ids) {
        return !preachformUnavailable("softDeleteByIds") && preachformOrNull().softDeleteByIds(ids);
    }
 
    @Override
    public boolean softDeleteBySubid(Long subid) {
        return !preachformUnavailable("softDeleteBySubid") && preachformOrNull().softDeleteBySubid(subid);
    }
 
    /**
     * 取数据服务实现;容器里没有、状态异常、或<b>只找到自己</b>时返回 {@code null}。
     * <p>
     * ⚠️ <b>必须过滤掉"自己"</b>,两个理由:
     * <ol>
     *   <li><b>自引用</b>:本类自身也实现了 {@link InhospVisitDataService}。若不过滤,
     *       容器在第一轮找不到别的实现时会把门面自己当候选返回,随后
     *       {@code dataService.selectPatMedInhospList(...)} 变成无限递归
     *       ({@code StackOverflowError})。这是满意度模块<b>实机踩到</b>的同类缺陷。</li>
     *   <li><b>循环依赖</b>:不过滤时 Spring 会在构造本 Bean 的过程中解析到"正在创建的自己",
     *       直接抛 {@code BeanCurrentlyInCreationException}。实测确认过这一点。</li>
     * </ol>
     * <p>
     * 用 {@code stream()} 而不是 {@code getIfAvailable()}:后者在候选多于一个时抛
     * {@code NoUniqueBeanDefinitionException},会被吞成 null 并掩盖"有人又加了一个实现"。
     */
    private InhospVisitDataService firstDataServiceOrNull(
            ObjectProvider<InhospVisitDataService> provider) {
        if (provider == null) {
            return null;
        }
        try {
            return provider.stream()
                    .filter(svc -> svc != this)
                    .findFirst()
                    .orElse(null);
        } catch (Exception e) {
            log.warn("【出入院随访】获取数据服务失败,基础出入院信息访问将降级:{}", e.toString());
            return null;
        }
    }
 
    /**
     * 供<b>测试/非 Spring 环境</b>直接构造(直接给出内部实现,可为 null)。
     * <p>
     * 开关取自 {@code sys_config} 的模拟键值,从而保证"开关校验"脚本无需 Spring/数据库即可运行。
     * <p>
     * 数据服务不传:测试构造下不提供基础出入院信息访问与宣教单访问
     * (调用会得到 null / 空集合 / false),保证"开关校验"类测试不必拉起数据库。
     */
    public InhospVisitFacade(InhospVisitProperties properties, InhospVisitApi delegate, String sysConfigValue) {
        this(properties, new InhospVisitSwitch(properties, sysConfigValue), new FixedProvider(delegate),
                null, null);
    }
 
    /**
     * 取运行时开关(供拦截器复用同一个实例、也供测试断言)
     */
    public InhospVisitSwitch getInhospVisitSwitch() {
        return inhospVisitSwitch;
    }
 
    /**
     * 取内部实现;模块关闭、实现缺失、或只找到自己时返回 {@code null}。
     * <p>
     * ⚠️ <b>必须排除"自己"</b>:本类自身也实现了 {@link InhospVisitApi},而 Spring 在
     * 按类型查找候选 Bean 时,若第一轮没找到别的实现(例如 ruoyi-inhospvisit
     * 没有被打进应用,或将来又有人用条件装配把它摘掉),会<b>把自引用当作兜底候选</b>返回。
     * 那样 {@code delegateOrNull()} 会拿到 {@code this},随后
     * {@code delegate.dealOutHospInfo(...)} 就变成无限递归({@code StackOverflowError})。
     * 这里显式过滤掉自己,把"没有实现"如实视为不可用。
     * —— 这是满意度模块<b>实机踩到</b>的缺陷。
     * <p>
     * ⚠️ <b>本方法不能替代开关判断</b>:开关关闭时 {@link DefaultInhospVisitApi} 依然存在,
     * 所以调用点一律先判 {@link #isEnabled()}。
     * <p>
     * 用 {@code stream()} 而不是 {@code getIfAvailable()}:后者在候选多于一个时抛
     * {@code NoUniqueBeanDefinitionException}(它继承自 {@code NoSuchBeanDefinitionException}),
     * 最终会被吞成 null,掩盖"有人又加了一个实现"这种配置问题。
     */
    private InhospVisitApi delegateOrNull() {
        if (apiProvider == null) {
            return null;
        }
        try {
            return apiProvider.stream()
                    .filter(api -> api != this)
                    .findFirst()
                    .orElse(null);
        } catch (Exception e) {
            // 容器状态异常时按"未启用"处理,绝不让调用方(定时任务、Controller)因为随访模块而失败
            log.warn("【出入院随访】获取内部实现失败,按未启用处理:{}", e.toString());
            return null;
        }
    }
 
    @Override
    public boolean isEnabled() {
        return inhospVisitSwitch.isEnabled() && delegateOrNull() != null;
    }
 
    @Override
    public int dealOutHospInfo(String config) {
        if (!isEnabled()) {
            log.warn("【出入院随访】模块未启用(参数配置 inhospvisit.enabled=false),dealOutHospInfo 跳过。config={}", config);
            return 0;
        }
        return delegateOrNull().dealOutHospInfo(config);
    }
 
    @Override
    public int dealOutHospInfoBySchedule(String config) {
        if (!isEnabled()) {
            log.warn("【出入院随访】模块未启用(参数配置 inhospvisit.enabled=false),dealOutHospInfoBySchedule 跳过。config={}", config);
            return 0;
        }
        return delegateOrNull().dealOutHospInfoBySchedule(config);
    }
 
    @Override
    public int addOperationSubTask(String config) {
        if (!isEnabled()) {
            log.warn("【出入院随访】模块未启用(参数配置 inhospvisit.enabled=false),addOperationSubTask 跳过。config={}", config);
            return 0;
        }
        return delegateOrNull().addOperationSubTask(config);
    }
 
    @Override
    public int dealOutpatientInfo() {
        if (!isEnabled()) {
            log.warn("【出入院随访】模块未启用(参数配置 inhospvisit.enabled=false),dealOutpatientInfo 跳过。");
            return 0;
        }
        return delegateOrNull().dealOutpatientInfo();
    }
 
    @Override
    public int rollTaskGroupCycle() {
        if (!isEnabled()) {
            log.warn("【出入院随访】模块未启用(参数配置 inhospvisit.enabled=false),rollTaskGroupCycle 跳过。");
            return 0;
        }
        return delegateOrNull().rollTaskGroupCycle();
    }
 
    @Override
    public com.ruoyi.common.core.service.dto.VisitAgainSubtaskDto buildAgainVisitDraft(String orgid, Long inhospid,
                                                                                      Long newTaskId, Long sendstate) {
        if (!isEnabled()) {
            log.warn("【出入院随访】模块未启用(参数配置 inhospvisit.enabled=false),buildAgainVisitDraft 返回空结果。"
                    + "orgid={}, inhospid={}", orgid, inhospid);
            return emptyAgainDraft("模块未启用");
        }
        return delegateOrNull().buildAgainVisitDraft(orgid, inhospid, newTaskId, sendstate);
    }
 
    @Override
    public boolean generatePublicitySubtask(Long taskid, Long patid, Long inhospid, int type, String config) {
        if (!isEnabled()) {
            log.warn("【出入院随访】模块未启用(参数配置 inhospvisit.enabled=false),generatePublicitySubtask 跳过。"
                    + "taskid={}, patid={}, type={}", taskid, patid, type);
            return false;
        }
        return delegateOrNull().generatePublicitySubtask(taskid, patid, inhospid, type, config);
    }
 
    // ==================================================================
    // 基础出入院信息读写(InhospVisitDataService)
    //
    // ⚠️ 与上面 6 个随访方法的关键区别:以下方法【不做开关判断】。
    //    理由见 InhospVisitDataService 类注释 —— 关闭随访生成不该让
    //    HIS 采集/档案汇聚无法写入院记录。
    //
    // ⚠️ 每个方法都对 dataService 判空:只有在"测试构造器"下它才是 null;
    //    Spring 环境下由 @Autowired 保证非 null。
    //    这里返回空值而非抛异常,保证调用方主流程不因本模块而失败。
    // ==================================================================
 
    private boolean dataUnavailable(String method) {
        if (dataService == null) {
            log.warn("【出入院随访】数据服务不可用(非 Spring 构造或装配缺失),{} 跳过。", method);
            return true;
        }
        return false;
    }
 
    @Override
    public com.ruoyi.inhospvisit.domain.PatMedInhosp selectPatMedInhospByInhospid(Long inhospid) {
        return dataUnavailable("selectPatMedInhospByInhospid") ? null
                : dataService.selectPatMedInhospByInhospid(inhospid);
    }
 
    @Override
    public java.util.List<com.ruoyi.inhospvisit.domain.PatMedInhosp> selectPatMedInhosp(
            com.ruoyi.inhospvisit.domain.PatMedInhosp patMedInhosp) {
        return dataUnavailable("selectPatMedInhosp") ? java.util.Collections.<com.ruoyi.inhospvisit.domain.PatMedInhosp>emptyList()
                : dataService.selectPatMedInhosp(patMedInhosp);
    }
 
    @Override
    public java.util.List<com.ruoyi.inhospvisit.domain.PatMedInhosp> selectOutInhospByPatnoAndOutDate(
            String patno, java.util.Date dayStart, java.util.Date dayEnd) {
        return dataUnavailable("selectOutInhospByPatnoAndOutDate")
                ? java.util.Collections.<com.ruoyi.inhospvisit.domain.PatMedInhosp>emptyList()
                : dataService.selectOutInhospByPatnoAndOutDate(patno, dayStart, dayEnd);
    }
 
    @Override
    public String getTagnameBypatid(Long patid) {
        return dataUnavailable("getTagnameBypatid") ? null : dataService.getTagnameBypatid(patid);
    }
 
    @Override
    public java.util.List<java.util.Map<String, Object>> getTagnameByPatids(java.util.List<Long> patids) {
        return dataUnavailable("getTagnameByPatids")
                ? java.util.Collections.<java.util.Map<String, Object>>emptyList()
                : dataService.getTagnameByPatids(patids);
    }
 
    @Override
    public java.util.List<com.ruoyi.inhospvisit.domain.PatMedInhosp> selectPatMedInhospList(
            com.ruoyi.inhospvisit.domain.PatMedInhosp patMedInhosp) {
        return dataUnavailable("selectPatMedInhospList")
                ? java.util.Collections.<com.ruoyi.inhospvisit.domain.PatMedInhosp>emptyList()
                : dataService.selectPatMedInhospList(patMedInhosp);
    }
 
    @Override
    public java.util.List<com.ruoyi.inhospvisit.domain.PatMedInhosp> selectPatMedInhospListBySerialnum(
            com.ruoyi.inhospvisit.domain.PatMedInhosp patMedInhosp) {
        return dataUnavailable("selectPatMedInhospListBySerialnum")
                ? java.util.Collections.<com.ruoyi.inhospvisit.domain.PatMedInhosp>emptyList()
                : dataService.selectPatMedInhospListBySerialnum(patMedInhosp);
    }
 
    @Override
    public java.util.List<com.ruoyi.inhospvisit.domain.PatMedInhosp> selectInhospRefreshCandidates(String orgid) {
        return dataUnavailable("selectInhospRefreshCandidates")
                ? java.util.Collections.<com.ruoyi.inhospvisit.domain.PatMedInhosp>emptyList()
                : dataService.selectInhospRefreshCandidates(orgid);
    }
 
    @Override
    public java.util.List<com.ruoyi.inhospvisit.domain.PatMedInhosp> selectPatMedInhospListByCondition(
            com.ruoyi.inhospvisit.domain.PatMedInhospVO patMedInhospVO) {
        return dataUnavailable("selectPatMedInhospListByCondition")
                ? java.util.Collections.<com.ruoyi.inhospvisit.domain.PatMedInhosp>emptyList()
                : dataService.selectPatMedInhospListByCondition(patMedInhospVO);
    }
 
    @Override
    public com.ruoyi.common.core.service.dto.PatMedRes selectPatMedInhospCount(
            com.ruoyi.common.core.service.dto.PatMedReq patMedReq) {
        return dataUnavailable("selectPatMedInhospCount") ? null : dataService.selectPatMedInhospCount(patMedReq);
    }
 
    @Override
    public java.util.List<com.ruoyi.inhospvisit.domain.PatMedInhosp> selectNeedProcessList() {
        return dataUnavailable("selectNeedProcessList")
                ? java.util.Collections.<com.ruoyi.inhospvisit.domain.PatMedInhosp>emptyList()
                : dataService.selectNeedProcessList();
    }
 
    /**
     * 查询患者住院记录(通过入院申请单信息)。
     * <p>
     * ⚠️ 降级时返回<b>空列表</b>(不是 null)—— 调用方的分页逻辑对 null 与空列表处理相同,
     * 但空列表能避免调用方额外判空。注意本方法在<b>正常</b>路径下也可能返回 {@code null}
     * (原实现的反向判断缺陷,见 {@code InhospVisitDataService} 的说明),那是业务行为,不是降级。
     */
    @Override
    public java.util.List<com.ruoyi.inhospvisit.domain.PatMedInhospDTO> selectPatMedInhospByApplication(
            com.ruoyi.inhospvisit.domain.PatMedInhosp patMedInhosp) {
        return dataUnavailable("selectPatMedInhospByApplication")
                ? java.util.Collections.<com.ruoyi.inhospvisit.domain.PatMedInhospDTO>emptyList()
                : dataService.selectPatMedInhospByApplication(patMedInhosp);
    }
 
    @Override
    public int insertPatMedInhosp(com.ruoyi.inhospvisit.domain.PatMedInhosp patMedInhosp) {
        return dataUnavailable("insertPatMedInhosp") ? 0 : dataService.insertPatMedInhosp(patMedInhosp);
    }
 
    @Override
    public int insertPatMedInhospBatch(java.util.List<com.ruoyi.inhospvisit.domain.PatMedInhosp> list) {
        return dataUnavailable("insertPatMedInhospBatch") ? 0 : dataService.insertPatMedInhospBatch(list);
    }
 
    @Override
    public int updatePatMedInhosp(com.ruoyi.inhospvisit.domain.PatMedInhosp patMedInhosp) {
        return dataUnavailable("updatePatMedInhosp") ? 0 : dataService.updatePatMedInhosp(patMedInhosp);
    }
 
    @Override
    public int deletePatMedInhospByInhospid(Long inhospid) {
        return dataUnavailable("deletePatMedInhospByInhospid") ? 0 : dataService.deletePatMedInhospByInhospid(inhospid);
    }
 
    @Override
    public int deletePatMedInhospByInhospno(String inhospno) {
        return dataUnavailable("deletePatMedInhospByInhospno") ? 0 : dataService.deletePatMedInhospByInhospno(inhospno);
    }
 
    @Override
    public int deletePatMedInhospByInhospids(Long[] inhospids) {
        return dataUnavailable("deletePatMedInhospByInhospids") ? 0
                : dataService.deletePatMedInhospByInhospids(inhospids);
    }
 
    /**
     * 空结果(键与"启用但组装失败"时一致,保证调用方不会 NPE)
     */
    public static com.ruoyi.common.core.service.dto.VisitAgainSubtaskDto emptyAgainDraft(String reason) {
        com.ruoyi.common.core.service.dto.VisitAgainSubtaskDto dto =
                new com.ruoyi.common.core.service.dto.VisitAgainSubtaskDto();
        dto.setDraft(null);
        dto.setInhospFound(false);
        dto.setMissReason(reason);
        return dto;
    }
 
    /**
     * 固定的 {@code ObjectProvider} 实现:仅用于非 Spring 环境/测试。
     */
    private static final class FixedProvider implements ObjectProvider<InhospVisitApi> {
 
        private final InhospVisitApi api;
 
        private FixedProvider(InhospVisitApi api) {
            this.api = api;
        }
 
        @Override
        public InhospVisitApi getObject(Object... args) {
            return api;
        }
 
        @Override
        public InhospVisitApi getIfAvailable() {
            return api;
        }
 
        @Override
        public InhospVisitApi getIfUnique() {
            return api;
        }
 
        @Override
        public InhospVisitApi getObject() {
            return api;
        }
 
        @Override
        public java.util.stream.Stream<InhospVisitApi> stream() {
            return api == null ? java.util.stream.Stream.empty() : java.util.stream.Stream.of(api);
        }
    }
}