liusheng
5 天以前 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
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
package com.ruoyi.othervisit.api;
 
import com.ruoyi.othervisit.config.OtherVisitProperties;
import com.ruoyi.othervisit.config.OtherVisitSwitch;
import com.ruoyi.othervisit.domain.BaseDictOperation;
import com.ruoyi.othervisit.domain.PatMedOperation;
import com.ruoyi.othervisit.domain.PatMedOperationItem;
import com.ruoyi.othervisit.service.BaseDictOperationService;
import com.ruoyi.othervisit.service.OperationItemService;
import com.ruoyi.othervisit.service.OperationService;
import com.ruoyi.othervisit.service.OperationVisitTaskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import java.util.List;
 
/**
 * 其他随访(手术随访)模块对外门面(<b>常驻 Bean</b>)。
 *
 * <h3>这是其它模块访问手术数据的<b>唯一入口</b></h3>
 * 按你定的跨模块数据访问原则:<b>别的模块要用手术数据,只能从这个模块调用,
 * 而不是在自己模块里写 SQL</b>。所以本门面包了两类能力:
 * <ol>
 *   <li><b>随访编排</b>:{@link #dealOperationInfo()};</li>
 *   <li><b>基础数据读写</b>:{@code pat_med_operation} / {@code pat_med_operation_item} /
 *       {@code base_dict_operation} 的增删改查。</li>
 * </ol>
 * 相应地,下面的调用方都必须改走本门面(本次一并改完):
 * <ul>
 *   <li>{@code ruoyi-quartz} 的 {@code RyTask#dealOperationInfo} 与 {@code CollectHISServiceImpl};</li>
 *   <li>smartor 的 {@code ServiceExternalServiceImpl}(HIS 外部手术回传)、
 *       {@code PatMedInhospServiceImpl}(出入院随访的手术分支)、
 *       {@code ServiceSubtaskServiceImpl}。</li>
 * </ul>
 *
 * <h3>为什么用常驻 Bean(不用 {@code @ConditionalOnProperty})</h3>
 * 与 {@code InhospVisitFacade} / {@code PublicityFacade} / {@code OuthospVisitFacade} 同一套设计:
 * <ul>
 *   <li><b>永远存在</b> —— 调用方({@code RyTask} 定时任务、{@code @Autowired})在任何开关状态下
 *       都<b>不会启动失败</b>;</li>
 *   <li>开关在 {@code sys_config} 的 {@code othervisit.enabled},由
 *       {@link OtherVisitSwitch} <b>运行时</b>读取(10 秒进程内缓存),改完无需重启;</li>
 *   <li>关闭时返回安全值并打 WARN,<b>不抛异常</b>,保证调用方主流程照常完成。</li>
 * </ul>
 *
 * <h3>⚠️ 哪些方法<b>不</b>受开关管辖</h3>
 * 下面所有「基础数据读写」方法都<b>不</b>判 {@code othervisit.enabled},
 * 理由与出入院/门诊模块一致:手术记录是 HIS 采集写入的原始业务数据,
 * 关掉"生成随访"不该让 HIS 采集写不进手术表 —— 那是数据故障,与"今天不发随访"不是一回事。
 * 只有 {@link #dealOperationInfo()} 受开关管辖。
 */
@Slf4j
@Component
public class OtherVisitFacade {
 
    private final OtherVisitProperties properties;
 
    /**
     * 运行时开关({@code sys_config} 优先,{@link OtherVisitProperties} 兜底)
     */
    private final OtherVisitSwitch otherVisitSwitch;
 
    /**
     * 手术随访编排(扫描未处理手术记录 → 匹配长期任务 → 写子任务)。
     * <p>
     * ⚠️ 用 {@code ObjectProvider} 惰性取:容器里若没有它也不该让门面装配失败。
     */
    private final ObjectProvider<OperationVisitTaskService> taskServiceProvider;
 
    /**
     * 手术记录基础读写(本模块自己的组件,没有条件装配,故直接注入)。
     */
    private final OperationService operationService;
 
    /**
     * 手术明细基础读写。
     */
    private final OperationItemService operationItemService;
 
    /**
     * 手术基础信息读写。
     */
    private final BaseDictOperationService baseDictOperationService;
 
    /**
     * Spring 使用的构造器。
     * <p>
     * ⚠️ <b>必须显式标注 {@code @Autowired}</b>:本类有两个公开构造器
     * (另一个是给测试/非 Spring 环境用的),若不标注,Spring 会因为"构造器不唯一"
     * 而退回查找无参构造器,从而在启动时报 {@code No default constructor found}。
     * —— 这是满意度模块<b>实机踩到</b>的缺陷,此处刻意保留同样的写法与注释。
     */
    @Autowired
    public OtherVisitFacade(OtherVisitProperties properties,
                            OtherVisitSwitch otherVisitSwitch,
                            ObjectProvider<OperationVisitTaskService> taskServiceProvider,
                            OperationService operationService,
                            OperationItemService operationItemService,
                            BaseDictOperationService baseDictOperationService) {
        this.properties = properties;
        this.otherVisitSwitch = otherVisitSwitch;
        this.taskServiceProvider = taskServiceProvider;
        this.operationService = operationService;
        this.operationItemService = operationItemService;
        this.baseDictOperationService = baseDictOperationService;
    }
 
    /**
     * 供<b>测试/非 Spring 环境</b>直接构造。
     * <p>
     * 所有数据服务与编排服务都不传:测试构造下调用它们会打 WARN 并安全返回,
     * 保证开关校验类测试不必拉起数据库。
     */
    public OtherVisitFacade(OtherVisitProperties properties, String sysConfigValue) {
        this(properties, new OtherVisitSwitch(properties, sysConfigValue), null, null, null, null);
    }
 
    /**
     * 取运行时开关(供测试断言 / 拦截器复用)
     */
    public OtherVisitSwitch getOtherVisitSwitch() {
        return otherVisitSwitch;
    }
 
    /**
     * 手术随访模块是否可用(开关打开)
     */
    public boolean isEnabled() {
        return otherVisitSwitch.isEnabled();
    }
 
    // ==========================================================================
    // 随访编排(受开关管辖)
    // ==========================================================================
 
    /**
     * 手术随访主扫描(供 {@code RyTask#dealOperationInfo} 调用)。
     *
     * <h3>⚠️ 本方法<b>受</b> {@code othervisit.enabled} 管辖</h3>
     * 它是"生成手术随访"的业务动作 —— 关掉开关就该停下。
     * 关闭时返回 {@code 0} 并打 WARN(不抛异常,保证定时任务主流程不被拖垮)。
     *
     * <h3>返回值说明</h3>
     * 正常时返回编排结果(原实现固定返回 1,<b>不是</b>处理条数);
     * 关闭或组件不可用时返回 0。原调用方 {@code RyTask} 忽略返回值,
     * 所以这个约定只影响日志与将来的排查。
     */
    public Integer dealOperationInfo() {
        if (!isEnabled()) {
            log.warn("【手术随访】模块未启用(参数配置 othervisit.enabled=false),dealOperationInfo 跳过。");
            return 0;
        }
        OperationVisitTaskService svc = taskService();
        if (svc == null) {
            log.warn("【手术随访】容器里没有 OperationVisitTaskService(或非 Spring 构造),dealOperationInfo 跳过。");
            return 0;
        }
        return svc.dealOperationInfo();
    }
 
    // ==========================================================================
    // 手术记录(pat_med_operation)—— 不受开关管辖
    // ==========================================================================
 
    /**
     * 查询手术记录。
     * <p>对应原 {@code IPatMedOperationService#selectPatMedOperationById}。
     */
    public PatMedOperation selectOperationById(Long id) {
        return operationService == null ? null : operationService.selectById(id);
    }
 
    /**
     * 查询手术记录列表。
     * <p>对应原 {@code IPatMedOperationService#selectPatMedOperationList}。
     */
    public List<PatMedOperation> selectOperationList(PatMedOperation query) {
        return operationService == null ? java.util.Collections.emptyList() : operationService.selectList(query);
    }
 
    /**
     * 新增手术记录。
     * <p>对应原 {@code IPatMedOperationService#insertPatMedOperation}。
     */
    public int insertOperation(PatMedOperation row) {
        return operationService == null ? 0 : operationService.insert(row);
    }
 
    /**
     * 修改手术记录。
     * <p>对应原 {@code IPatMedOperationService#updatePatMedOperation}。
     */
    public int updateOperation(PatMedOperation row) {
        return operationService == null ? 0 : operationService.update(row);
    }
 
    /**
     * 批量删除手术记录。
     * <p>对应原 {@code IPatMedOperationService#deletePatMedOperationByIds}。
     */
    public int deleteOperationByIds(Long[] ids) {
        return operationService == null ? 0 : operationService.deleteByIds(ids);
    }
 
    /**
     * 删除手术记录。
     * <p>对应原 {@code IPatMedOperationService#deletePatMedOperationById}。
     */
    public int deleteOperationById(Long id) {
        return operationService == null ? 0 : operationService.deleteById(id);
    }
 
    // ==========================================================================
    // 手术明细(pat_med_operation_item)—— 不受开关管辖
    // ==========================================================================
 
    public PatMedOperationItem selectOperationItemById(Long id) {
        return operationItemService == null ? null : operationItemService.selectById(id);
    }
 
    public List<PatMedOperationItem> selectOperationItemList(PatMedOperationItem query) {
        return operationItemService == null ? java.util.Collections.emptyList() : operationItemService.selectList(query);
    }
 
    /**
     * 按条件查手术明细({@code JOIN pat_med_inhosp} 的那条)。
     * <p>
     * ⚠️ 与 {@link #selectOperationItemList(PatMedOperationItem)} <b>不是同一条 SQL</b>,别混用。
     */
    public List<PatMedOperationItem> selectOperationItemListByCondition(PatMedOperationItem query) {
        return operationItemService == null ? java.util.Collections.emptyList()
                : operationItemService.selectOperationItemList(query);
    }
 
    /**
     * 按条件查手术明细,同一 {@code opid} 下只取 {@code oplevelcode} 最大的一条。
     * <p>调用方:出入院随访的手术分支({@code PatMedInhospServiceImpl})。
     */
    public List<PatMedOperationItem> selectOperationItemListMaxLevel(PatMedOperationItem query) {
        return operationItemService == null ? java.util.Collections.emptyList()
                : operationItemService.selectOperationItemListMaxLevel(query);
    }
 
    public int insertOperationItem(PatMedOperationItem row) {
        return operationItemService == null ? 0 : operationItemService.insert(row);
    }
 
    public int insertOperationItems(List<PatMedOperationItem> rows) {
        return operationItemService == null ? 0 : operationItemService.insertBatch(rows);
    }
 
    public int updateOperationItem(PatMedOperationItem row) {
        return operationItemService == null ? 0 : operationItemService.update(row);
    }
 
    /**
     * 按 {@code operationid} 更新手术明细。
     * <p>⚠️ 不代设 {@code updateTime}(原直调 Mapper 的调用方自己负责)。
     */
    public int updateOperationItemByOperationid(PatMedOperationItem row) {
        return operationItemService == null ? 0 : operationItemService.updateByOperationid(row);
    }
 
    public int deleteOperationItemByIds(Long[] ids) {
        return operationItemService == null ? 0 : operationItemService.deleteByIds(ids);
    }
 
    public int deleteOperationItemById(Long id) {
        return operationItemService == null ? 0 : operationItemService.deleteById(id);
    }
 
    /**
     * 按 {@code opid} <b>物理</b>删除手术明细。
     */
    public int deleteHardOperationItemByOpId(String opid) {
        return operationItemService == null ? 0 : operationItemService.deleteHardByOpId(opid);
    }
 
    /**
     * 按 {@code opid} <b>软</b>删除手术明细。
     */
    public int deleteSoftOperationItemByOpId(String opid) {
        return operationItemService == null ? 0 : operationItemService.deleteSoftByOpId(opid);
    }
 
    /**
     * 按 {@code operationid} 删除手术明细。
     */
    public int deleteOperationItemByOperationid(Long operationid) {
        return operationItemService == null ? 0 : operationItemService.deleteByOperationid(operationid);
    }
 
    // ==========================================================================
    // 手术基础信息(base_dict_operation)—— 不受开关管辖
    // ==========================================================================
 
    public BaseDictOperation selectBaseDictOperationById(Long id) {
        return baseDictOperationService == null ? null : baseDictOperationService.selectById(id);
    }
 
    public List<BaseDictOperation> selectBaseDictOperationList(BaseDictOperation query) {
        return baseDictOperationService == null ? java.util.Collections.emptyList()
                : baseDictOperationService.selectList(query);
    }
 
    public int insertBaseDictOperation(BaseDictOperation row) {
        return baseDictOperationService == null ? 0 : baseDictOperationService.insert(row);
    }
 
    public int updateBaseDictOperation(BaseDictOperation row) {
        return baseDictOperationService == null ? 0 : baseDictOperationService.update(row);
    }
 
    public int deleteBaseDictOperationByIds(Long[] ids) {
        return baseDictOperationService == null ? 0 : baseDictOperationService.deleteByIds(ids);
    }
 
    public int deleteBaseDictOperationById(Long id) {
        return baseDictOperationService == null ? 0 : baseDictOperationService.deleteById(id);
    }
 
    // ==========================================================================
 
    private OperationVisitTaskService taskService() {
        if (taskServiceProvider == null) {
            return null;
        }
        try {
            return taskServiceProvider.stream().findFirst().orElse(null);
        } catch (Exception e) {
            log.warn("【手术随访】获取 OperationVisitTaskService 失败,按不可用处理:{}", e.toString());
            return null;
        }
    }
}