Files
his/openhis-server-new/openhis-domain/src/main/resources/mapper/administration/ScheduleSlotMapper.xml
wangjian963 cbad13bddc Fix: 门诊预约挂号→签到→退号 slot/pool 状态流转对齐需求
- 枚举重排: SlotStatus LOCKED=4→2, CANCELLED=2→4,匹配需求编号
  - 预约: lockSlotForBooking 写入 LOCKED(2) 替代 BOOKED(1),pool locked_num+1 原子递增
  - 签到: LOCKED(2)→BOOKED(1) 替代 CHECKED_IN(3),加前置状态校验
  - 退号: 加 BOOKED(1) 前置校验
  - 池计数: refreshPoolStats booked_num=COUNT(1), locked_num=COUNT(2)
  - SQL 状态值全部由 SlotStatus 枚举传入,消除硬编码
  - 查询/显示: 加 locked 筛选分支,BOOKED→已取号, LOCKED→已锁定
  - 前端常量同步,签到列表查询 book→locked
2026-05-19 12:12:16 +08:00

462 lines
19 KiB
XML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.openhis.appointmentmanage.mapper.ScheduleSlotMapper">
<!--
统一状态值映射: DB 数值 → 规范化输出
0=待约 1=已约(签到后) 2=锁定(预约后) 3=已签到 4=已停诊 5=已退号
-->
<sql id="slotStatusNormExpr">
CASE
WHEN LOWER(CONCAT('', s.status)) IN ('0', 'unbooked', 'available') THEN 0
WHEN LOWER(CONCAT('', s.status)) IN ('1', 'booked') THEN 1
WHEN LOWER(CONCAT('', s.status)) IN ('2', 'locked') THEN 2
WHEN LOWER(CONCAT('', s.status)) IN ('3', 'checked', 'checked_in', 'checkin') THEN 3
WHEN LOWER(CONCAT('', s.status)) IN ('4', 'cancelled', 'canceled', 'stopped') THEN 4
WHEN LOWER(CONCAT('', s.status)) IN ('5', 'returned') THEN 5
ELSE NULL
END
</sql>
<sql id="orderStatusNormExpr">
CASE
WHEN LOWER(CONCAT('', o.status)) IN ('1', 'booked') THEN 1
WHEN LOWER(CONCAT('', o.status)) IN ('2', 'checked', 'checked_in', 'checkin') THEN 2
WHEN LOWER(CONCAT('', o.status)) IN ('3', 'cancelled', 'canceled') THEN 3
WHEN LOWER(CONCAT('', o.status)) IN ('4', 'returned') THEN 4
ELSE NULL
END
</sql>
<sql id="poolStatusNormExpr">
CASE
WHEN LOWER(CONCAT('', p.status)) IN ('0', 'unbooked', 'available') THEN 0
WHEN LOWER(CONCAT('', p.status)) IN ('1', 'booked') THEN 1
WHEN LOWER(CONCAT('', p.status)) IN ('2', 'locked') THEN 2
WHEN LOWER(CONCAT('', p.status)) IN ('3', 'checked', 'checked_in', 'checkin') THEN 3
WHEN LOWER(CONCAT('', p.status)) IN ('4', 'cancelled', 'canceled', 'stopped') THEN 4
WHEN LOWER(CONCAT('', p.status)) IN ('5', 'returned') THEN 5
ELSE NULL
END
</sql>
<!-- 注意这里的 resultType 指向了您刚建好的 DTO 实体类 -->
<select id="selectAllTicketSlots" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
SELECT
s.id AS slotId,
s.seq_no AS seqNo,
p.schedule_id AS scheduleId,
p.doctor_name AS doctor,
p.dept_id AS departmentId,
p.fee AS fee,
o.patient_id AS patientId,
o.patient_name AS patientName,
o.medical_card AS medicalCard,
o.phone AS phone,
<include refid="orderStatusNormExpr" /> AS orderStatus,
<include refid="slotStatusNormExpr" /> AS slotStatus,
s.expect_time AS expectTime,
p.schedule_date AS scheduleDate,
d.reg_type AS regType,
<include refid="poolStatusNormExpr" /> AS poolStatus,
p.stop_reason AS stopReason,
d.is_stopped AS isStopped
FROM
adm_schedule_slot s
INNER JOIN adm_schedule_pool p ON s.pool_id = p.id
LEFT JOIN adm_doctor_schedule d ON p.schedule_id = d.id
LEFT JOIN (
SELECT DISTINCT
ON (slot_id) slot_id,
patient_id,
patient_name,
medical_card,
phone,
status
FROM
order_main
WHERE
LOWER(CONCAT('', status)) IN ('1', '2', '4', 'booked', 'checked', 'checked_in', 'checkin', 'returned')
ORDER BY
slot_id,
create_time DESC
) o ON o.slot_id = s.id
WHERE
p.delete_flag = '0'
AND s.delete_flag = '0'
ORDER BY
p.schedule_date,
p.doctor_id,
s.expect_time,
s.seq_no ASC
</select>
<select id="selectTicketSlotById" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
SELECT
s.id AS slotId,
s.seq_no AS seqNo,
p.schedule_id AS scheduleId,
p.doctor_name AS doctor,
p.doctor_id AS doctorId,
p.dept_id AS departmentId,
org.name AS departmentName,
p.fee AS fee,
o.patient_id AS patientId,
o.patient_name AS patientName,
o.medical_card AS medicalCard,
o.phone AS phone,
o.id AS orderId,
o.order_no AS orderNo,
COALESCE(CAST(o.gender AS VARCHAR), CAST(pinfo.gender_enum AS VARCHAR)) AS patientGender,
pinfo.gender_enum AS genderEnum,
pinfo.id_card AS idCard,
o.appointment_time AS appointmentTime,
<include refid="orderStatusNormExpr" /> AS orderStatus,
<include refid="slotStatusNormExpr" /> AS slotStatus,
s.expect_time AS expectTime,
p.schedule_date AS scheduleDate,
d.reg_type AS regType,
<include refid="poolStatusNormExpr" /> AS poolStatus,
p.stop_reason AS stopReason,
d.is_stopped AS isStopped
FROM
adm_schedule_slot s
INNER JOIN adm_schedule_pool p ON s.pool_id = p.id
LEFT JOIN adm_doctor_schedule d ON p.schedule_id = d.id
LEFT JOIN adm_organization org ON p.dept_id = org.id
AND org.delete_flag = '0'
LEFT JOIN (
SELECT DISTINCT
ON (slot_id) slot_id,
patient_id,
patient_name,
medical_card,
phone,
id,
order_no,
gender,
appointment_time,
status
FROM
order_main
WHERE
LOWER(CONCAT('', status)) IN ('1', '2', '4', 'booked', 'checked', 'checked_in', 'checkin', 'returned')
ORDER BY
slot_id,
create_time DESC
) o ON o.slot_id = s.id
LEFT JOIN adm_patient pinfo ON o.patient_id = pinfo.id
WHERE
s.id = #{id}
</select>
<!-- 预约锁定: 0→#{lockedStatus} (AVAILABLE→LOCKED),由枚举传入 -->
<update id="lockSlotForBooking">
UPDATE adm_schedule_slot
SET
status = #{lockedStatus},
update_time = now()
WHERE
id = #{slotId}
AND status = 0
AND delete_flag = '0'
</update>
<!-- status=0(待约)时清空order_id释放号源使退号后号源可再被预约 -->
<update id="updateSlotStatus">
UPDATE adm_schedule_slot
SET
status = #{status},
<if test="status != null and status == 0">
order_id = NULL,
</if>
update_time = now()
WHERE
id = #{slotId}
AND delete_flag = '0'
</update>
<!-- 签到: #{requiredStatus}→#{status} (LOCKED→BOOKED),前置条件由枚举传入 -->
<update id="updateSlotStatusAndCheckInTime">
UPDATE adm_schedule_slot
SET
status = #{status},
check_in_time = #{checkInTime},
update_time = NOW()
WHERE
id = #{slotId}
AND status = #{requiredStatus}
AND delete_flag = '0'
</update>
<select id="selectPoolIdBySlotId" resultType="java.lang.Long">
SELECT
pool_id
FROM
adm_schedule_slot
WHERE
id = #{slotId}
AND delete_flag = '0'
</select>
<update id="bindOrderToSlot">
UPDATE adm_schedule_slot
SET
order_id = #{orderId},
update_time = now()
WHERE
id = #{slotId}
AND status = 2
AND delete_flag = '0'
</update>
<select id="selectTicketSlotsPage" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
SELECT
s.id AS slotId,
s.seq_no AS seqNo,
p.schedule_id AS scheduleId,
p.doctor_name AS doctor,
p.doctor_id AS doctorId,
p.dept_id AS departmentId,
org.name AS departmentName,
p.fee AS fee,
o.patient_id AS patientId,
o.patient_name AS patientName,
o.medical_card AS medicalCard,
o.phone AS phone,
o.id AS orderId,
o.order_no AS orderNo,
COALESCE(CAST(o.gender AS VARCHAR), CAST(pinfo.gender_enum AS VARCHAR)) AS patientGender,
pinfo.gender_enum AS genderEnum,
pinfo.id_card AS idCard,
o.appointment_time AS appointmentTime,
<include refid="orderStatusNormExpr" /> AS orderStatus,
<include refid="slotStatusNormExpr" /> AS slotStatus,
s.expect_time AS expectTime,
p.schedule_date AS scheduleDate,
d.reg_type AS regType,
<include refid="poolStatusNormExpr" /> AS poolStatus,
p.stop_reason AS stopReason,
d.is_stopped AS isStopped
FROM
adm_schedule_slot s
INNER JOIN adm_schedule_pool p ON s.pool_id = p.id
LEFT JOIN adm_doctor_schedule d ON p.schedule_id = d.id
LEFT JOIN adm_organization org ON p.dept_id = org.id
AND org.delete_flag = '0'
LEFT JOIN (
SELECT DISTINCT
ON (slot_id) slot_id,
patient_id,
patient_name,
medical_card,
phone,
id,
order_no,
gender,
appointment_time,
status
FROM
order_main
WHERE
LOWER(CONCAT('', status)) IN ('1', '2', '4', 'booked', 'checked', 'checked_in', 'checkin', 'returned')
ORDER BY
slot_id,
create_time DESC
) o ON o.slot_id = s.id
LEFT JOIN adm_patient pinfo ON o.patient_id = pinfo.id
<where>
p.delete_flag = '0'
AND s.delete_flag = '0'
<!-- 1. 按日期查 -->
<if test="query.date != null and query.date != ''">
AND p.schedule_date = CAST(#{query.date} AS DATE)
</if>
<!-- 2. 按科室查 -->
<if test="query.department != null and query.department != '' and query.department != 'all'">
AND org.name = #{query.department}
</if>
<if test="query.doctorId != null">
AND p.doctor_id = #{query.doctorId}
</if>
<!-- 3. 按号源类型查 (专家/普通) -->
<if test="query.type != null and query.type != ''">
<choose>
<when test="query.type == 'expert'">
AND d.reg_type = 1
</when>
<when test="query.type == 'general'">
AND (
d.reg_type != 1
OR d.reg_type IS NULL
)
</when>
</choose>
</if>
<!-- 4. 模糊搜索患者信息 -->
<if test="query.name != null and query.name != ''">
AND o.patient_name LIKE CONCAT('%', #{query.name}, '%')
</if>
<if test="query.card != null and query.card != ''">
AND o.medical_card LIKE CONCAT('%', #{query.card}, '%')
</if>
<if test="query.phone != null and query.phone != ''">
AND o.phone LIKE CONCAT('%', #{query.phone}, '%')
</if>
<!-- 5. 时间过滤: 仅待约(0)受时间限制,已锁定(2)/已约(1)/已签到(3)/已退号(5)不受影响 -->
AND (
(<include refid="slotStatusNormExpr" /> = 0 AND (p.schedule_date > CURRENT_DATE OR (p.schedule_date = CURRENT_DATE AND (CAST(p.schedule_date AS TIMESTAMP) + CAST(s.expect_time AS TIME)) >= NOW())))
OR <include refid="slotStatusNormExpr" /> = 1
OR <include refid="slotStatusNormExpr" /> = 2
OR <include refid="slotStatusNormExpr" /> = 3
OR <include refid="slotStatusNormExpr" /> = 5
OR <include refid="orderStatusNormExpr" /> = 4
)
<!-- 6. 状态筛选: unbooked(0) locked(2) booked(2) checked(1) cancelled(4) returned(5) -->
<if test="query.status != null and query.status != '' and query.status != 'all'">
<choose>
<when test="'unbooked'.equals(query.status) or '未预约'.equals(query.status)">
AND <include refid="slotStatusNormExpr" /> = 0
AND (
d.is_stopped IS NULL
OR d.is_stopped = FALSE
)
</when>
<when test="'booked'.equals(query.status) or '已预约'.equals(query.status)">
AND <include refid="slotStatusNormExpr" /> = 2
AND <include refid="orderStatusNormExpr" /> = 1
AND (
d.is_stopped IS NULL
OR d.is_stopped = FALSE
)
</when>
<when test="'locked'.equals(query.status) or '已锁定'.equals(query.status)">
AND <include refid="slotStatusNormExpr" /> = 2
AND <include refid="orderStatusNormExpr" /> = 1
AND (
d.is_stopped IS NULL
OR d.is_stopped = FALSE
)
</when>
<when test="'checked'.equals(query.status) or '已取号'.equals(query.status)">
AND <include refid="slotStatusNormExpr" /> = 1
AND (
d.is_stopped IS NULL
OR d.is_stopped = FALSE
)
</when>
<when test="'cancelled'.equals(query.status) or '已停诊'.equals(query.status) or '已取消'.equals(query.status)">
AND (
<include refid="slotStatusNormExpr" /> = 4
OR d.is_stopped = TRUE
)
</when>
<when test="'returned'.equals(query.status) or '已退号'.equals(query.status)">
AND (
<include refid="slotStatusNormExpr" /> = 5
OR <include refid="orderStatusNormExpr" /> = 4
)
</when>
<otherwise>
AND 1 = 2
</otherwise>
</choose>
</if>
</where>
ORDER BY
p.schedule_date DESC,
s.expect_time ASC,
s.seq_no ASC
</select>
<select id="selectDoctorAvailabilitySummary" resultType="com.openhis.appointmentmanage.domain.DoctorAvailabilityDTO">
SELECT
p.doctor_id AS doctorId,
p.doctor_name AS doctorName,
p.schedule_date AS scheduleDate,
<!-- 直接 COUNT 未预约的号源,前端已经做了时间过滤,这里只按日期统计 -->
COUNT(
CASE
WHEN s.delete_flag = '0'
AND <include refid="slotStatusNormExpr" /> = 0
<!-- 使用前端传来的当前时间戳过滤过期号源,保证和前端完全一致 -->
AND (
p.schedule_date > CURRENT_DATE
OR (
p.schedule_date = CURRENT_DATE
AND (CAST(p.schedule_date AS TIMESTAMP) + CAST(s.expect_time AS TIME)) >= NOW()
)
)
THEN s.id
ELSE NULL
END
) AS available,
COUNT(DISTINCT p.id) AS poolCount,
CASE
WHEN MAX(
CASE
WHEN d.reg_type = 1 THEN 1
ELSE 0
END
) = 1 THEN 'expert'
ELSE 'general'
END AS ticketType
FROM
adm_schedule_pool p
LEFT JOIN adm_doctor_schedule d ON p.schedule_id = d.id
LEFT JOIN adm_organization org ON p.dept_id = org.id AND org.delete_flag = '0'
LEFT JOIN adm_schedule_slot s ON s.pool_id = p.id AND s.delete_flag = '0'
<where>
p.delete_flag = '0'
<!-- 排除医生已停诊的号源 -->
AND (d.is_stopped IS NULL OR d.is_stopped = FALSE)
<!-- 过滤未来号源:只统计当前日期及未来日期的号源 -->
<if test="query.date != null and query.date != ''">
AND p.schedule_date = CAST(#{query.date} AS DATE)
</if>
<!-- 增加时间过滤:排除已过去的就诊日期 -->
AND (p.schedule_date > CURRENT_DATE OR (p.schedule_date = CURRENT_DATE AND (CAST(p.schedule_date AS TIMESTAMP) + CAST(s.expect_time AS TIME)) >= NOW()))
<if test="query.department != null and query.department != '' and query.department != 'all'">
AND org.name = #{query.department}
</if>
<if test="query.type != null and query.type != '' and query.type != 'all'">
<choose>
<when test="query.type == 'expert'">
AND d.reg_type = 1
</when>
<when test="query.type == 'general'">
AND (
d.reg_type != 1
OR d.reg_type IS NULL
)
</when>
</choose>
</if>
<if test="query.doctorId != null">
AND p.doctor_id = #{query.doctorId}
</if>
</where>
GROUP BY
p.doctor_id,
p.doctor_name,
p.schedule_date
ORDER BY
p.schedule_date ASC,
p.doctor_name ASC
</select>
<select id="selectSeqNoBySlotIds" resultType="com.openhis.appointmentmanage.domain.ScheduleSlot">
SELECT id, seq_no
FROM adm_schedule_slot
WHERE id IN
<foreach collection="slotIds" item="slotId" open="(" separator="," close=")">
#{slotId}
</foreach>
AND delete_flag = '0'
</select>
</mapper>