yxh
7 天以前 68ff33beff11310e830b0615b5a81f83fad0f683
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
package cn.lihu.jh.module.ecg.service.appointment;
 
import org.junit.Test;
 
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
 
import static org.junit.Assert.*;
 
/**
 * 锁定 HIS「开单时间」({@code author/time/value}) 的解析行为。
 *
 * <h3>背景(2026-10 排查结论)</h3>
 * 库中约 61% 的 {@code appointment.doctor_time} 为 {@code yyyy-MM-dd 00:00:00}。
 * 经核查**不是**本项目的格式化缺陷(列是 datetime、写入 java.util.Date、
 * 真实报文样例带时分秒),而是 **HIS 侧把该字段的时分秒发成了 {@code 000000}**。
 * 证据:有真实时刻的记录呈正常工作时段分布(06:00→08:00 高峰),
 * 落在开诊前 00:00-05:59 的仅 15 条;门诊(book_src=1) 99.6%、住院(book_src=2) 90.5% 为 0 点。
 *
 * <p>因此本次修复**不改变写入值**(保留 HIS 原值,日期仍有意义),只做两件事:
 * <ol>
 *   <li>把原先「静默丢弃」的解析失败改为带<b>报文原值 + 申请单号</b>的 WARN 日志;</li>
 *   <li>识别「日期值」形态并打 WARN,便于向 HIS 侧反馈。</li>
 * </ol>
 *
 * <p>本测试通过反射直接验证私有解析方法,保证上述行为不被回退。
 */
public class HisDoctorTimeParseTest {
 
    /** 反射调用私有方法 parseHisDoctorTime(String, String) */
    private Object invokeParse(String raw) throws Exception {
        AppointmentServiceImpl svc = new AppointmentServiceImpl();
        Method m = AppointmentServiceImpl.class.getDeclaredMethod(
                "parseHisDoctorTime", String.class, String.class);
        m.setAccessible(true);
        return m.invoke(svc, raw, "TEST-APPLY-NO");
    }
 
    /** 反射调用私有方法 isHisTimeOnlyDate(String) */
    private boolean invokeIsDateOnly(String raw) throws Exception {
        AppointmentServiceImpl svc = new AppointmentServiceImpl();
        Method m = AppointmentServiceImpl.class.getDeclaredMethod("isHisTimeOnlyDate", String.class);
        m.setAccessible(true);
        return (Boolean) m.invoke(svc, raw);
    }
 
    /** 正常形态:14 位且带时分秒 —— 必须原样解析出真实时刻 */
    @Test
    public void testNormalTime_shouldParseExactMoment() throws Exception {
        Object parsed = invokeParse("20260917005255");
        assertNotNull("14 位带时分秒的值必须解析成功", parsed);
        String s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(parsed);
        assertEquals("2026-09-17 00:52:55", s);
        System.out.println("[正常形态] 20260917005255 -> " + s);
    }
 
    /** 「日期值」形态:时分秒=000000 —— 仍要写入(保留日期),只是会打 WARN */
    @Test
    public void testDateOnlyValue_shouldStillParseAndKeepDate() throws Exception {
        Object parsed = invokeParse("20261001000000");
        assertNotNull("日期值形态仍应解析成功(日期有意义,不能丢)", parsed);
        String s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(parsed);
        assertEquals("2026-10-01 00:00:00", s);
        assertTrue("应被识别为日期值", invokeIsDateOnly("20261001000000"));
        System.out.println("[日期值形态] 20261001000000 -> " + s + "(日期保留,时刻为 HIS 所发原值)");
    }
 
    /** 真实时刻不应被误判为日期值 */
    @Test
    public void testRealTime_shouldNotBeTreatedAsDateOnly() throws Exception {
        assertFalse(invokeIsDateOnly("20260917005255"));
        assertFalse(invokeIsDateOnly("20261001120000"));
        assertFalse(invokeIsDateOnly("20261001000001"));
        System.out.println("[误判防护] 带真实时分秒的值均未被判定为日期值");
    }
 
    /** 非 14 位(HIS 文档样例中出现过 12 位 yyyyMMddHHmm)—— 返回 null,不写库 */
    @Test
    public void testNonFourteenDigit_shouldReturnNull() throws Exception {
        assertNull("12 位不应被解析(原实现会静默丢弃,现改为 WARN)", invokeParse("201205061000"));
        assertNull("空串不应被解析", invokeParse(""));
        assertNull("null 不应被解析", invokeParse(null));
        System.out.println("[非14位] 12 位/空/null 均返回 null(并打 WARN),不会写坏数据");
    }
 
    /** 非法格式不应抛异常(避免影响整条报文落库) */
    @Test
    public void testInvalidFormat_shouldNotThrow() throws Exception {
        assertNull(invokeParse("abc"));
        assertNull(invokeParse("20261301000000")); // 13 月
        System.out.println("[健壮性] 非法格式返回 null 且不抛异常");
    }
}