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 且不抛异常");
|
}
|
}
|