yxh
6 天以前 68ff33beff11310e830b0615b5a81f83fad0f683
jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/dal/mysql/appointment/AppointmentMapper.java
@@ -25,7 +25,9 @@
    default PageResult<AppointmentDO> selectPage(AppointmentPageReqVO reqVO) {
        LambdaQueryWrapperX<AppointmentDO> wrapper = (LambdaQueryWrapperX<AppointmentDO>) new LambdaQueryWrapperX<AppointmentDO>().eqIfPresent(AppointmentDO::getPatId, reqVO.getPatId()).likeIfPresent(AppointmentDO::getPatName, reqVO.getPatName()).eqIfPresent(AppointmentDO::getPatGender, reqVO.getPatGender()).eqIfPresent(AppointmentDO::getPatBirthday, reqVO.getPatBirthday()).eqIfPresent(AppointmentDO::getPatMobile, reqVO.getPatMobile()).eqIfPresent(AppointmentDO::getPatPhone, reqVO.getPatPhone()).eqIfPresent(AppointmentDO::getPatIdentityId, reqVO.getPatIdentityId()).eqIfPresent(AppointmentDO::getPatAddr, reqVO.getPatAddr()).eqIfPresent(AppointmentDO::getPatDeptCode, reqVO.getPatDeptCode()).eqIfPresent(AppointmentDO::getPatDeptDesc, reqVO.getPatDeptDesc()).eqIfPresent(AppointmentDO::getPatWardCode, reqVO.getPatWardCode()).eqIfPresent(AppointmentDO::getPatWardDesc, reqVO.getPatWardDesc()).eqIfPresent(AppointmentDO::getBookDate, reqVO.getBookDate()).eqIfPresent(AppointmentDO::getBookTimeslot, reqVO.getBookTimeslot()).eqIfPresent(AppointmentDO::getBookCheckType, reqVO.getBookCheckType()).eqIfPresent(AppointmentDO::getBookSrc, reqVO.getBookSrc()).eqIfPresent(AppointmentDO::getPaid, reqVO.getPaid()).eqIfPresent(AppointmentDO::getApplyNo, reqVO.getApplyNo()).eqIfPresent(AppointmentDO::getEpisodeId, reqVO.getEpisodeId()).inIfPresent(AppointmentDO::getStatus, reqVO.getStatus())
                // ✅ 新增:createTime 区间查询
                .betweenIfPresent(AppointmentDO::getCreateTime, reqVO.getCreateTimeStart(), reqVO.getCreateTimeEnd()).betweenIfPresent(AppointmentDO::getRegisterDate, reqVO.getRegisterDateStart(), reqVO.getRegisterDateEnd());
                .betweenIfPresent(AppointmentDO::getCreateTime, reqVO.getCreateTimeStart(), reqVO.getCreateTimeEnd()).betweenIfPresent(AppointmentDO::getRegisterDate, reqVO.getRegisterDateStart(), reqVO.getRegisterDateEnd())
                // 【本需求】预约日期**时间段**查询(预约记录页的「预约日期」日期范围)
                .betweenIfPresent(AppointmentDO::getBookDate, reqVO.getBookDateStart(), reqVO.getBookDateEnd());
        // 动态排序
        if (reqVO.getOrderCreateTime() != null) {
            wrapper.orderByDesc(AppointmentDO::getCreateTime);
@@ -36,21 +38,75 @@
    }
    @Select("select * from lihu.appointment where pat_id = #{patId} and to_days(book_date) = to_days(Now())")
    @Select("select * from lihu.appointment where deleted = 0 and pat_id = #{patId} and to_days(book_date) = to_days(Now())")
    List<AppointmentDO> getCurrentPatId(String patId);
    @Select("select * from lihu.appointment where (apply_no = #{code} or episode_id= #{code} or hospital_no =#{code} or pat_identity_id=#{code}) and book_date >=  DATE_ADD(CURDATE(), INTERVAL -5 DAY)    AND book_date <=  DATE_ADD(CURDATE(), INTERVAL 15 DAY)")
    /**
     * 【本需求】按「患者编号 / 申请单号 / 就诊流水号 / 住院号 / 身份证号」匹配预约(±5/+15 天窗口)。
     *
     * <h3>为什么加 {@code pat_id} 与 {@code deleted = 0}</h3>
     * <ul>
     *   <li><b>pat_id</b>:手动新建的预约({@code /ecg/appointment/create})只有 pat_id,
     *       {@code apply_no} / {@code episode_id} / {@code hospital_no} / {@code pat_identity_id} 全为空,
     *       签到页按患者编号查询时永远查不到 → 提示「请先预约」→ 无法签到;</li>
     *   <li><b>deleted = 0</b>:原 SQL 是裸查询,逻辑删除的旧行也会被捞出来,
     *       再经 {@code queryAndCreateAppointmentByPatId} 的"不存在则插入"逻辑可能把已删的单再插回来。</li>
     * </ul>
     */
    @Select("select * from lihu.appointment where deleted = 0 "
            + " and (apply_no = #{code} or episode_id = #{code} or hospital_no = #{code} "
            + "      or pat_identity_id = #{code} or pat_id = #{code}) "
            + " and book_date >= DATE_ADD(CURDATE(), INTERVAL -5 DAY) "
            + " and book_date <= DATE_ADD(CURDATE(), INTERVAL 15 DAY) "
            + " order by book_date, id")
    List<AppointmentDO> getCurrentCode(String code);
    @Select("select * from lihu.appointment where pat_id = #{patId} and book_check_type = #{checkType} and to_days(book_date) = to_days(Now()) limit 1")
    AppointmentDO getByPatAndCheckTypeAndBookDate(@Param("patId") String patId, @Param("checkType") Integer checkType);
    /**
     * 【本需求:紧急患者不排队直接检查】按「申请单号 / 就诊号 / 患者编号」查近一个月的预约记录。
     *
     * <p>窗口 = [今天-30 天, 今天+30 天];结果**按预约时间倒序**(最近的排前面),便于医生选择。
     * 检查项目过滤(本窗口开展的检查项目)由 {@code checkTypeList} 传入,空/未传表示不限制。
     */
    @Select("<script>" +
            "select * from lihu.appointment where deleted = 0 " +
            " and (apply_no = #{code} or episode_id = #{code} or pat_id = #{code}) " +
            " and book_date &gt;= DATE_ADD(CURDATE(), INTERVAL -30 DAY) " +
            " and book_date &lt;= DATE_ADD(CURDATE(), INTERVAL 30 DAY) " +
            " <if test='checkTypeList != null and checkTypeList.size() > 0'> " +
            "   and book_check_type in ( <foreach collection='checkTypeList' separator=',' item='ct'>#{ct}</foreach> ) " +
            " </if> " +
            " order by book_date desc, book_timeslot desc, id desc " +
            "</script>")
    List<AppointmentDO> getEmergencyCandidates(@Param("code") String code,
                                               @Param("checkTypeList") List<Integer> checkTypeList);
    Integer updateStatusByApplyNo(@Param("applyNo") String applyNo, @Param("status") String status, @Param("registerDate") Date registerDate);
    @Select("select * from lihu.appointment where episode_id = #{episodeId} and apply_no = #{applyNo} limit 1")
    /**
     * 只更新状态(不动其它字段)。
     *
     * <p>背景:状态流原来是把 {@code getByApplyNo} 查出来的整行对象再 {@code updateById} 写回,
     * 属于「读-改-写」。并发/交错时会把别的字段(如 book_date / book_check_type)按旧快照覆盖掉,
     * 实测 2026-10-05 07:58 就出现过一笔预约的 check_type 被状态流覆盖。
     * 状态流只该改状态,因此单独提供这个只改 status 的语句。</p>
     */
    @Update("update lihu.appointment set status = #{status}, update_time = sysdate() where apply_no = #{applyNo} and deleted = 0")
    Integer updateStatusOnlyByApplyNo(@Param("applyNo") String applyNo, @Param("status") String status);
    /** 【本次修复】补 {@code deleted = 0}:逻辑删除的行不应再被当作"已存在"而跳过插入。 */
    @Select("select * from lihu.appointment where deleted = 0 and episode_id = #{episodeId} and apply_no = #{applyNo} limit 1")
    AppointmentDO getByEpisodeIdAndApplyNo(@Param("episodeId") String episodeId, @Param("applyNo") String applyNo);
    @Select("select * from lihu.appointment where apply_no = #{applyNo} limit 1")
    /**
     * 按申请单号取未删除的记录。
     *
     * <p>注意:这里必须带 {@code deleted = 0}。原实现是裸 select,会把**逻辑删除**的旧行也捞出来,
     * 配合 {@code LIMIT 1} 时若旧行 id 更小就会取到已删除的那条 —— 这是"删了又被更新回来"的隐患。
     * 另外本方法配合唯一索引 {@code uk_appointment_apply_no_active}(只约束未删除行)使用。</p>
     */
    @Select("select * from lihu.appointment where apply_no = #{applyNo} and deleted = 0 limit 1")
    AppointmentDO getByApplyNo(@Param("applyNo") String applyNo);
    /**
@@ -65,5 +121,19 @@
            " </foreach> )" +
            "</script>")
    List<AppointmentDO> getByApplyNoList(@Param("applyNoList") List<String> applyNoList);
    /**
     * 取该患者**最近一条**未删除记录,用于容错新增时继承患者固有属性
     * (生日/电话/身份证/地址等 —— S050401 报文只带姓名,这些字段报文里没有)。
     *
     * <p>按 {@code create_time desc, id desc} 排序,保证结果稳定(同一秒内插入的多条也能定序)。
     * 只继承**患者固有属性**,不继承开单医生/开单时间等流水属性(那属于推断,可能张冠李戴)。</p>
     *
     * @param patId 患者编号({@code appointment.pat_id})
     * @return 最近一条记录;无记录时返回 {@code null}
     */
    @Select("select * from lihu.appointment where pat_id = #{patId} and deleted = 0 "
            + "order by create_time desc, id desc limit 1")
    AppointmentDO getLatestByPatId(@Param("patId") String patId);
}