506 门诊挂号:门诊诊前退号后,数据库多表状态值变更与 PRD 定义不符

CommonConstants.AppointmentOrderStatus 常量 → OrderStatus 枚举重构
   新增枚举:0=患者取消 / 1=有效 / 2=系统取消 / 3=已完成
   退号流程加乐观锁防并发,slot 状态改回待约,退号日志独立事务 修复 XML 中 Integer 比较用字符串的问题
Bug #411 — 诊室过滤栏从科室下拉框改为诊室按钮组
This commit is contained in:
wangjian963
2026-05-11 13:51:47 +08:00
parent f9ab4c5688
commit df6c5f3824
9 changed files with 259 additions and 172 deletions

View File

@@ -10,7 +10,7 @@ import com.openhis.clinical.service.ITicketService;
import com.openhis.web.appointmentmanage.appservice.ITicketAppService; import com.openhis.web.appointmentmanage.appservice.ITicketAppService;
import com.openhis.web.appointmentmanage.dto.TicketDto; import com.openhis.web.appointmentmanage.dto.TicketDto;
import com.openhis.common.constant.CommonConstants.SlotStatus; import com.openhis.common.constant.CommonConstants.SlotStatus;
import com.openhis.common.constant.CommonConstants.AppointmentOrderStatus; import com.openhis.common.enums.OrderStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@@ -198,10 +198,11 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (SlotStatus.CHECKED_IN.equals(slotStatus)) { if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
dto.setStatus("已取号"); dto.setStatus("已取号");
} else if (SlotStatus.BOOKED.equals(slotStatus)) { } else if (SlotStatus.BOOKED.equals(slotStatus)) {
if (AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus())) { // order_main.status: 0=患者取消(已退号) 2=系统取消 其余=已预约
dto.setStatus("已取号"); if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
} else if (AppointmentOrderStatus.RETURNED.equals(raw.getOrderStatus())) {
dto.setStatus("已退号"); dto.setStatus("已退号");
} else if (OrderStatus.SYSTEM_CANCELLED.getValue().equals(raw.getOrderStatus())) {
dto.setStatus("系统取消");
} else { } else {
dto.setStatus("已预约"); dto.setStatus("已预约");
} }
@@ -372,10 +373,11 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (SlotStatus.CHECKED_IN.equals(slotStatus)) { if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
dto.setStatus("已取号"); dto.setStatus("已取号");
} else if (SlotStatus.BOOKED.equals(slotStatus)) { } else if (SlotStatus.BOOKED.equals(slotStatus)) {
if (AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus())) { // order_main.status: 0=患者取消(已退号) 2=系统取消 其余=已预约
dto.setStatus("已取号"); if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
} else if (AppointmentOrderStatus.RETURNED.equals(raw.getOrderStatus())) {
dto.setStatus("已退号"); dto.setStatus("已退号");
} else if (OrderStatus.SYSTEM_CANCELLED.getValue().equals(raw.getOrderStatus())) {
dto.setStatus("系统取消");
} else { } else {
dto.setStatus("已预约"); dto.setStatus("已预约");
} }

View File

@@ -2,6 +2,7 @@ package com.openhis.web.chargemanage.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
@@ -329,16 +330,14 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
} }
} }
// 如果本次门诊挂号来自预约签到,同步预约订单与号源槽位状态改为已退号 // 退费成功后,同步回滚预约订单状态及号源;同时移除分诊队列
Long refundOrderMainId = null;
if (result != null && result.getCode() == 200) { if (result != null && result.getCode() == 200) {
syncAppointmentReturnStatus(byId, cancelRegPaymentDto.getReason()); refundOrderMainId = syncAppointmentReturnStatus(byId, cancelRegPaymentDto.getReason());
// 同步移除分诊队列中的记录
removeTriageQueueItem(byId.getId()); removeTriageQueueItem(byId.getId());
} }
// 退号日志独立事务写入,无论退费成功与否均记录
// 记录退号日志 recordRefundLog(cancelRegPaymentDto, byId, result, paymentRecon, refundOrderMainId);
recordRefundLog(cancelRegPaymentDto, byId, result, paymentRecon);
// 2025/05/05 该处保存费用项后,会通过统一收费处理进行收费 // 2025/05/05 该处保存费用项后,会通过统一收费处理进行收费
return R.ok(paymentRecon, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] {"退号"})); return R.ok(paymentRecon, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] {"退号"}));
@@ -435,8 +434,6 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
// 通过患者、科室、日期查找关联的预约订单 // 通过患者、科室、日期查找关联的预约订单
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>() LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounter.getPatientId()) .eq(Order::getPatientId, encounter.getPatientId())
.in(Order::getStatus, CommonConstants.AppointmentOrderStatus.BOOKED,
CommonConstants.AppointmentOrderStatus.CHECKED_IN)
.orderByDesc(Order::getUpdateTime) .orderByDesc(Order::getUpdateTime)
.orderByDesc(Order::getCreateTime) .orderByDesc(Order::getCreateTime)
.last("LIMIT 1"); .last("LIMIT 1");
@@ -590,20 +587,25 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
} }
/** /**
* 同步预约号源状态为已退号 * 诊前退号:回滚预约订单、号源槽位、号源池统计
* 说明: *
* 1) 门诊退号主流程不依赖该步骤成功与否,因此此方法内部异常仅记录日志,不向上抛出。 * <p>处理四件事:
* 2) 通过患者、科室、日期以及状态筛选最近一条预约订单,尽量避免误匹配。 * <ol>
* <li>order_main → status=0(患者取消), pay_status=3(已退费), 写入取消时间和原因</li>
* <li>adm_schedule_slot → status=0(待约), order_id=NULL(释放号源)</li>
* <li>adm_schedule_pool → 重算统计值 + version+1</li>
* <li>返回 order_main.id 供 refund_log 关联</li>
* </ol>
*
* <p>异常仅记录日志不向上抛,不影响主流程返回成功。
*/ */
private void syncAppointmentReturnStatus(Encounter encounter, String reason) { private Long syncAppointmentReturnStatus(Encounter encounter, String reason) {
if (encounter == null || encounter.getPatientId() == null) { if (encounter == null || encounter.getPatientId() == null) {
return; return null;
} }
try { try {
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>() LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounter.getPatientId()) .eq(Order::getPatientId, encounter.getPatientId())
.in(Order::getStatus, CommonConstants.AppointmentOrderStatus.BOOKED,
CommonConstants.AppointmentOrderStatus.CHECKED_IN)
.orderByDesc(Order::getUpdateTime) .orderByDesc(Order::getUpdateTime)
.orderByDesc(Order::getCreateTime) .orderByDesc(Order::getCreateTime)
.last("LIMIT 1"); .last("LIMIT 1");
@@ -625,35 +627,55 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
Order appointmentOrder = orderService.getOne(queryWrapper, false); Order appointmentOrder = orderService.getOne(queryWrapper, false);
if (appointmentOrder == null) { if (appointmentOrder == null) {
return; return null;
} }
Date now = new Date(); // 只有有效订单(1)才能退号
if (!CommonConstants.AppointmentOrderStatus.RETURNED.equals(appointmentOrder.getStatus())) { if (!OrderStatus.ACTIVE.getValue().equals(appointmentOrder.getStatus())) {
Order updateOrder = new Order(); log.warn("退号跳过:订单状态非有效, orderId={}, status={}",
updateOrder.setId(appointmentOrder.getId()); appointmentOrder.getId(), appointmentOrder.getStatus());
updateOrder.setStatus(CommonConstants.AppointmentOrderStatus.RETURNED); return null;
updateOrder.setPayStatus(0); }
updateOrder.setCancelTime(now);
updateOrder.setCancelReason("门诊退号"); // 乐观锁更新WHERE version = 旧值,防并发重复退号
updateOrder.setUpdateTime(now); boolean updated = orderService.update(
orderService.updateById(updateOrder); new LambdaUpdateWrapper<Order>()
.set(Order::getStatus, OrderStatus.PATIENT_CANCELLED.getValue())
.set(Order::getPayStatus, PaymentStatus.REFUND_ALL.getValue())
.set(Order::getCancelTime, new Date())
.set(Order::getCancelReason,
StringUtils.isNotEmpty(reason) ? reason : "诊前退号")
.set(Order::getUpdateTime, new Date())
.setSql("version = version + 1")
.eq(Order::getId, appointmentOrder.getId())
.eq(Order::getVersion, appointmentOrder.getVersion())
);
if (!updated) {
log.warn("退号乐观锁冲突,订单已被其他操作修改, orderId={}", appointmentOrder.getId());
return null;
} }
Long slotId = appointmentOrder.getSlotId(); Long slotId = appointmentOrder.getSlotId();
if (slotId == null) { if (slotId == null) {
return; return appointmentOrder.getId();
} }
int slotRows = scheduleSlotMapper.updateSlotStatus(slotId, CommonConstants.SlotStatus.RETURNED); int slotRows = scheduleSlotMapper.updateSlotStatus(slotId, CommonConstants.SlotStatus.AVAILABLE);
if (slotRows > 0) { if (slotRows > 0) {
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId); Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
if (poolId != null) { if (poolId != null) {
schedulePoolMapper.refreshPoolStats(poolId); schedulePoolMapper.refreshPoolStats(poolId);
schedulePoolMapper.update(null,
new LambdaUpdateWrapper<SchedulePool>()
.setSql("version = version + 1")
.set(SchedulePool::getUpdateTime, new Date())
.eq(SchedulePool::getId, poolId));
} }
} }
return appointmentOrder.getId();
} catch (Exception e) { } catch (Exception e) {
log.warn("同步预约号源已退号状态失败, encounterId={}", encounter.getId(), e); log.warn("同步预约号源已退号状态失败, encounterId={}", encounter.getId(), e);
return null;
} }
} }
@@ -672,22 +694,29 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
} }
/** /**
* 记录退号日志 * 记录退号日志(独立事务)。
*
* <p>REQUIRES_NEW 确保即使主事务回滚,退号审计日志也不丢失。
* orderMainId 优先使用 order_main.id若退费失败则 fallback 到 encounterId。
* *
* @param cancelRegPaymentDto 退号请求对象 * @param cancelRegPaymentDto 退号请求对象
* @param encounter 就诊信息 * @param encounter 就诊信息
* @param result 退号结果 * @param result 退号结果
* @param paymentRecon 支付对账信息 * @param paymentRecon 支付对账信息
* @param orderMainId 预约订单主键order_main.id用于关联业务数据
*/ */
@Transactional(propagation = Propagation.REQUIRES_NEW) @Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordRefundLog(CancelRegPaymentDto cancelRegPaymentDto, public void recordRefundLog(CancelRegPaymentDto cancelRegPaymentDto,
Encounter encounter, Encounter encounter,
R<?> result, R<?> result,
PaymentReconciliation paymentRecon) { PaymentReconciliation paymentRecon,
Long orderMainId) {
RefundLog refundLog = new RefundLog(); RefundLog refundLog = new RefundLog();
try { try {
// 1. 订单ID唯一 // 1. 订单ID关联 order_main.id
String orderId = String.valueOf(cancelRegPaymentDto.getEncounterId()); String orderId = orderMainId != null
? String.valueOf(orderMainId)
: String.valueOf(cancelRegPaymentDto.getEncounterId());
refundLog.setOrderId(orderId); refundLog.setOrderId(orderId);
// 已存在则不重复插入(防止唯一约束异常) // 已存在则不重复插入(防止唯一约束异常)

View File

@@ -1991,7 +1991,7 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
Order appointmentOrder = iOrderService.getOne( Order appointmentOrder = iOrderService.getOne(
new LambdaQueryWrapper<Order>() new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounterFormData.getPatientId()) .eq(Order::getPatientId, encounterFormData.getPatientId())
.eq(Order::getStatus, CommonConstants.AppointmentOrderStatus.CHECKED_IN) .eq(Order::getStatus, OrderStatus.ACTIVE.getValue()) // 有效订单(1)
.eq(Order::getDeleteFlag, "0") .eq(Order::getDeleteFlag, "0")
.orderByDesc(Order::getCreateTime) .orderByDesc(Order::getCreateTime)
.last("LIMIT 1") .last("LIMIT 1")
@@ -2114,11 +2114,11 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
Long queuePoolId = null; Long queuePoolId = null;
Long queueSlotId = null; Long queueSlotId = null;
try { try {
// 查询患者当天的待签到预约订单status = 1 或 2 表示已预约或已取号) // 查询患者当天有效订单(1);已取消(0/2)和已完成(3)的不参与排队
Order order = iOrderService.getOne( Order order = iOrderService.getOne(
new LambdaQueryWrapper<Order>() new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounter.getPatientId()) .eq(Order::getPatientId, encounter.getPatientId())
.in(Order::getStatus, 1, 2) // 1=BOOKED 已预约, 2=CHECKED_IN 已取号 .eq(Order::getStatus, OrderStatus.ACTIVE.getValue()) // 有效(1)
.eq(Order::getDeleteFlag, "0") .eq(Order::getDeleteFlag, "0")
.orderByDesc(Order::getCreateTime) .orderByDesc(Order::getCreateTime)
.last("LIMIT 1") .last("LIMIT 1")

View File

@@ -0,0 +1,63 @@
/*
* Copyright ©2023 CJB-CNIT Team. All rights reserved
*/
package com.openhis.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 订单状态 (order_main.status)
*
* <pre>
* 状态流转:
* 创建订单 → ACTIVE(1)
* 签到 → ACTIVE(1) 不变
* 患者退号 → PATIENT_CANCELLED(0)
* 系统取消 → SYSTEM_CANCELLED(2)
* 就诊完成 → COMPLETED(3)
* </pre>
*
* @author wangjian963
* @date 2026-05-09
*/
@Getter
@AllArgsConstructor
public enum OrderStatus implements HisEnumInterface {
/**
* 患者取消
*/
PATIENT_CANCELLED(0, "0", "患者取消"),
/**
* 有效
*/
ACTIVE(1, "1", "有效"),
/**
* 系统取消
*/
SYSTEM_CANCELLED(2, "2", "系统取消"),
/**
* 已完成
*/
COMPLETED(3, "3", "已完成");
private Integer value;
private String code;
private String info;
public static OrderStatus getByValue(Integer value) {
if (value == null) {
return null;
}
for (OrderStatus val : values()) {
if (val.getValue().equals(value)) {
return val;
}
}
return null;
}
}

View File

@@ -6,8 +6,8 @@ import com.core.common.utils.AssignSeqUtil;
import com.openhis.clinical.domain.Order; import com.openhis.clinical.domain.Order;
import com.openhis.clinical.mapper.OrderMapper; import com.openhis.clinical.mapper.OrderMapper;
import com.openhis.clinical.service.IOrderService; import com.openhis.clinical.service.IOrderService;
import com.openhis.common.constant.CommonConstants.AppointmentOrderStatus;
import com.openhis.common.enums.AssignSeqEnum; import com.openhis.common.enums.AssignSeqEnum;
import com.openhis.common.enums.OrderStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@@ -124,7 +124,8 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
: new Date(); // 兜底:正常业务不应走到这里 : new Date(); // 兜底:正常业务不应走到这里
order.setAppointmentDate(appointmentDateTime); order.setAppointmentDate(appointmentDateTime);
order.setAppointmentTime(appointmentDateTime); order.setAppointmentTime(appointmentDateTime);
order.setStatus(AppointmentOrderStatus.BOOKED); // 订单状态: 0=患者取消 1=有效 2=系统取消 3=已完成
order.setStatus(OrderStatus.ACTIVE.getValue());
order.setPayStatus(0); order.setPayStatus(0);
order.setVersion(0); order.setVersion(0);
@@ -169,10 +170,13 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
if (order == null) { if (order == null) {
throw new RuntimeException("订单不存在"); throw new RuntimeException("订单不存在");
} }
if (AppointmentOrderStatus.CANCELLED.equals(order.getStatus())) { // 已取消患者取消0 或 系统取消2不可再次取消
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(order.getStatus())
|| OrderStatus.SYSTEM_CANCELLED.getValue().equals(order.getStatus())) {
throw new RuntimeException("订单已取消"); throw new RuntimeException("订单已取消");
} }
if (AppointmentOrderStatus.CHECKED_IN.equals(order.getStatus())) { // 已完成(3)的订单不可取消
if (OrderStatus.COMPLETED.getValue().equals(order.getStatus())) {
throw new RuntimeException("订单已完成,无法取消"); throw new RuntimeException("订单已完成,无法取消");
} }
@@ -189,6 +193,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
.eq(Order::getPatientId, patientId) .eq(Order::getPatientId, patientId)
.eq(Order::getTenantId, tenantId) .eq(Order::getTenantId, tenantId)
.ge(Order::getCancelTime, startTime) .ge(Order::getCancelTime, startTime)
.eq(Order::getStatus, AppointmentOrderStatus.CANCELLED)); // 只统计患者主动取消(0),不含系统取消(2)
.eq(Order::getStatus, OrderStatus.PATIENT_CANCELLED.getValue()));
} }
} }

View File

@@ -13,8 +13,8 @@ import com.openhis.clinical.domain.Ticket;
import com.openhis.clinical.mapper.TicketMapper; import com.openhis.clinical.mapper.TicketMapper;
import com.openhis.clinical.service.IOrderService; import com.openhis.clinical.service.IOrderService;
import com.openhis.clinical.service.ITicketService; import com.openhis.clinical.service.ITicketService;
import com.openhis.common.constant.CommonConstants.AppointmentOrderStatus;
import com.openhis.common.constant.CommonConstants.SlotStatus; import com.openhis.common.constant.CommonConstants.SlotStatus;
import com.openhis.common.enums.OrderStatus;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -195,8 +195,8 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
Date startTime = Date.from(periodStart.atZone(ZoneId.systemDefault()).toInstant()); Date startTime = Date.from(periodStart.atZone(ZoneId.systemDefault()).toInstant());
Date endTime = Date.from(periodEnd.atZone(ZoneId.systemDefault()).toInstant()); Date endTime = Date.from(periodEnd.atZone(ZoneId.systemDefault()).toInstant());
// 预约去重以订单为准order_main因为预约成功会先落订单clinical_ticket 不一定在此链路写入 // 预约去重以订单为准order_main有效订单(1)才参与去重
List<Integer> effectiveOrderStatuses = Arrays.asList(AppointmentOrderStatus.BOOKED, AppointmentOrderStatus.CHECKED_IN); List<Integer> effectiveOrderStatuses = Arrays.asList(OrderStatus.ACTIVE.getValue());
int exists = orderMapper.countPatientDeptOrdersInPeriod(dto.getPatientId(), slot.getDepartmentId(), slot.getDepartmentName(), int exists = orderMapper.countPatientDeptOrdersInPeriod(dto.getPatientId(), slot.getDepartmentId(), slot.getDepartmentName(),
startTime, endTime, effectiveOrderStatuses); startTime, endTime, effectiveOrderStatuses);
if (exists > 0) { if (exists > 0) {
@@ -314,9 +314,8 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
} }
Order latestOrder = orders.get(0); Order latestOrder = orders.get(0);
// 1. 更新订单状态为已取号,并更新支付状态和支付时间 // 1. 签到不改变订单状态(仍为有效1),更新支付状态为已支付并记录支付时间
orderService.updateOrderStatusById(latestOrder.getId(), AppointmentOrderStatus.CHECKED_IN); orderService.updateOrderStatusById(latestOrder.getId(), OrderStatus.ACTIVE.getValue());
// 更新支付状态为已支付,记录支付时间
orderMapper.updatePayStatus(latestOrder.getId(), 1, new Date()); orderMapper.updatePayStatus(latestOrder.getId(), 1, new Date());
// 2. 查询号源槽位信息 // 2. 查询号源槽位信息

View File

@@ -160,11 +160,12 @@
AND delete_flag = '0' AND delete_flag = '0'
</update> </update>
<!-- status=0(待约)时清空order_id释放号源使退号后号源可再被预约 -->
<update id="updateSlotStatus"> <update id="updateSlotStatus">
UPDATE adm_schedule_slot UPDATE adm_schedule_slot
SET SET
status = #{status}, status = #{status},
<if test="status != null and '0'.equals(status.toString())"> <if test="status != null and status == 0">
order_id = NULL, order_id = NULL,
</if> </if>
update_time = now() update_time = now()

View File

@@ -117,12 +117,14 @@
</where> </where>
</select> </select>
<!-- status=1: 只查有效订单(0=患者取消 1=有效 2=系统取消 3=已完成) -->
<select id="selectOrderById" resultMap="OrderResult"> <select id="selectOrderById" resultMap="OrderResult">
select * from order_main where id = #{id} select * from order_main where id = #{id}
and status = 1 and status = 1
order by create_time desc order by create_time desc
</select> </select>
<!-- status=1: 只查有效订单 -->
<select id="selectOrderBySlotId" resultMap="OrderResult"> <select id="selectOrderBySlotId" resultMap="OrderResult">
select * from order_main where slot_id = #{slotId} and status = 1 select * from order_main where slot_id = #{slotId} and status = 1
</select> </select>
@@ -248,8 +250,9 @@
update order_main set status = #{status} where id = #{id} update order_main set status = #{status} where id = #{id}
</update> </update>
<!-- status=0: 患者取消 (OrderStatus.PATIENT_CANCELLED) -->
<update id="updateOrderCancelInfoById"> <update id="updateOrderCancelInfoById">
update order_main set status = 3, cancel_time = #{cancelTime}, cancel_reason = #{cancelReason} where id = #{id} update order_main set status = 0, cancel_time = #{cancelTime}, cancel_reason = #{cancelReason} where id = #{id}
</update> </update>
<update id="updatePayStatus"> <update id="updatePayStatus">

View File

@@ -198,53 +198,52 @@
</el-button> </el-button>
</div> </div>
<div class="queue-actions-right">
<el-button
:type="showOnlyWaiting ? 'primary' : ''"
size="small"
@click="showOnlyWaiting = true"
>
只显示等待
</el-button>
<el-button
:type="!showOnlyWaiting ? 'primary' : ''"
size="small"
@click="showOnlyWaiting = false"
>
显示全部状态
</el-button>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- 底部控制面板 --> <!-- 底部控制面板 -->
<div class="footer-section"> <div class="footer-section">
<!-- 诊室快速过滤栏 --> <!-- Bug #411诊室快速过滤栏筛选维度从科室改为诊室 -->
<div class="filter-section"> <div class="filter-section">
<div class="filter-label"> <div class="filter-left">
诊室快速过滤栏 <div class="filter-label">
</div> 诊室快速过滤栏
<div class="filter-select-wrapper"> </div>
<el-select <div class="filter-button-wrapper">
v-model="selectedClinicRoom" <el-button
placeholder="请选择诊室" :type="selectedRoom === 'all' ? 'primary' : ''"
clearable size="small"
filterable @click="selectedRoom = 'all'"
style="width: 100%" >
size="default" 全部
> </el-button>
<el-option <el-button
label="全部" v-for="room in uniqueRooms"
value="all"
/>
<el-option
v-for="room in clinicRoomList"
:key="room" :key="room"
:label="room" :type="selectedRoom === room ? 'primary' : ''"
:value="room" size="small"
/> @click="selectedRoom = room"
</el-select> >
{{ room }}
</el-button>
</div>
</div>
<div class="filter-right">
<el-button
:type="showOnlyWaiting ? 'primary' : ''"
size="small"
@click="showOnlyWaiting = true"
>
只显示等待
</el-button>
<el-button
:type="!showOnlyWaiting ? 'primary' : ''"
size="small"
@click="showOnlyWaiting = false"
>
显示全部状态
</el-button>
</div> </div>
</div> </div>
@@ -680,10 +679,8 @@ const selectedCandidates = ref([])
// 显示选项 // 显示选项
const showOnlyWaiting = ref(false) const showOnlyWaiting = ref(false)
// 诊室过滤(按诊室维度筛选 // Bug #411诊室过滤替代原来的科室下拉框selectedDept/departmentList 已移除
const selectedClinicRoom = ref('all') const selectedRoom = ref('all')
// 诊室列表(从数据中动态提取)
const clinicRoomList = ref([])
// 修复【#397】动态获取当前科室名称 // 修复【#397】动态获取当前科室名称
const currentDeptName = computed(() => { const currentDeptName = computed(() => {
@@ -906,11 +903,12 @@ const mapFrontendStatusToBackend = (status) => {
// 从数据库加载队列 // 从数据库加载队列
const loadQueueFromDb = async () => { const loadQueueFromDb = async () => {
try { try {
// 使用当前登录人科室 // Bug #411不再按科室选筛加载后端默认按当前登录人科室查询
const organizationId = undefined
// 只查询今天的患者
const today = new Date() const today = new Date()
const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}` const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`
console.log('【心内科】loadQueueFromDb 开始date=', todayStr) const res = await getTriageQueueList({ organizationId, date: todayStr }).catch((err) => {
const res = await getTriageQueueList({ date: todayStr }).catch((err) => {
console.error('【心内科】loadQueueFromDb 请求异常:', err) console.error('【心内科】loadQueueFromDb 请求异常:', err)
return { code: 500, msg: err?.message || '请求失败', data: null } return { code: 500, msg: err?.message || '请求失败', data: null }
}) })
@@ -1138,8 +1136,6 @@ const loadDataFromApi = async () => {
// 同步当前呼叫(队列从 DB 加载后已同步;这里再兜底一次) // 同步当前呼叫(队列从 DB 加载后已同步;这里再兜底一次)
syncCurrentCallFromQueue() syncCurrentCallFromQueue()
// 提取诊室列表供过滤栏使用
extractClinicRooms()
console.log('【心内科】数据加载完成:候选池', originalCandidatePoolList.value.length, '条,队列', originalQueueList.value.length, '条') console.log('【心内科】数据加载完成:候选池', originalCandidatePoolList.value.length, '条,队列', originalQueueList.value.length, '条')
ElMessage.success('【心内科】已从门诊挂号接口加载数据') ElMessage.success('【心内科】已从门诊挂号接口加载数据')
} catch (e) { } catch (e) {
@@ -1151,42 +1147,35 @@ const loadDataFromApi = async () => {
totalSignedIn.value = originalCandidatePoolList.value.length totalSignedIn.value = originalCandidatePoolList.value.length
totalInQueue.value = originalQueueList.value.length totalInQueue.value = originalQueueList.value.length
syncCurrentCallFromQueue() syncCurrentCallFromQueue()
extractClinicRooms()
} }
} }
// 原始数据存储(用于过滤) // 原始数据存储(用于过滤)
const originalCandidatePoolList = ref(getInitialCandidatePoolList()) const originalCandidatePoolList = ref(getInitialCandidatePoolList())
// 提取诊室列表(从队列和候选池数据中动态获取 // 过滤后的智能候选池数据(按诊室过滤
const extractClinicRooms = () => {
const roomSet = new Set()
// 从队列中提取
originalQueueList.value.forEach(item => {
if (item.room && item.room !== '-') {
roomSet.add(item.room)
}
})
// 从候选池中提取
originalCandidatePoolList.value.forEach(item => {
if (item.room && item.room !== '-') {
roomSet.add(item.room)
}
})
clinicRoomList.value = Array.from(roomSet).sort()
}
// 过滤后的智能候选池数据
const filteredCandidatePoolList = computed(() => { const filteredCandidatePoolList = computed(() => {
if (selectedClinicRoom.value === 'all') { if (selectedRoom.value === 'all') {
return originalCandidatePoolList.value return originalCandidatePoolList.value
} }
return originalCandidatePoolList.value.filter(item => item.room === selectedClinicRoom.value) return originalCandidatePoolList.value.filter(item => item.room === selectedRoom.value)
}) })
// 原始队列数据存储(用于过滤) // 原始队列数据存储(用于过滤)
const originalQueueList = ref(getInitialQueueList()) const originalQueueList = ref(getInitialQueueList())
// 动态计算已加载数据中的唯一诊室列表(依赖上方两个 ref确保声明顺序正确
const uniqueRooms = computed(() => {
const rooms = new Set()
originalCandidatePoolList.value.forEach(item => {
if (item.room && item.room !== '-') rooms.add(item.room)
})
originalQueueList.value.forEach(item => {
if (item.room && item.room !== '-') rooms.add(item.room)
})
return Array.from(rooms).sort()
})
const parseMmSsToSeconds = (mmss) => { const parseMmSsToSeconds = (mmss) => {
if (!mmss || typeof mmss !== 'string') return 0 if (!mmss || typeof mmss !== 'string') return 0
const [mm, ss] = mmss.split(':') const [mm, ss] = mmss.split(':')
@@ -1203,7 +1192,7 @@ const formatSecondsToMmSs = (totalSeconds) => {
return `${mm}:${ss}` return `${mm}:${ss}`
} }
// 过滤后的智能队列数据(同时考虑诊室过滤状态过滤) // 过滤后的智能队列数据(Bug #411诊室过滤 + 状态过滤)
const filteredQueueList = computed(() => { const filteredQueueList = computed(() => {
let filtered = originalQueueList.value let filtered = originalQueueList.value
@@ -1211,8 +1200,8 @@ const filteredQueueList = computed(() => {
filtered = filtered.filter(item => item.status !== '已完成') filtered = filtered.filter(item => item.status !== '已完成')
// 再按诊室过滤 // 再按诊室过滤
if (selectedClinicRoom.value !== 'all') { if (selectedRoom.value !== 'all') {
filtered = filtered.filter(item => item.room === selectedClinicRoom.value) filtered = filtered.filter(item => item.room === selectedRoom.value)
} }
// 再按状态过滤(只显示等待) // 再按状态过滤(只显示等待)
@@ -1723,16 +1712,12 @@ const handleNextPatient = async () => {
reqData.id = selectedQueueRow.value.id reqData.id = selectedQueueRow.value.id
reqData.organizationId = selectedQueueRow.value.organizationId reqData.organizationId = selectedQueueRow.value.organizationId
} else { } else {
// 如果没有选中患者,使用查询条件(兼容旧逻辑) // Bug #411已移除 selectedDept改为从队列数据中动态获取科室
// 全科模式:优先用"当前叫号中/第一个等待"所在科室 const calling = originalQueueList.value.find((i) => i.status === '叫号中')
let orgId = null const waiting = originalQueueList.value.find((i) => i.status === '等待')
{ console.log('【心内科】handleNextPatient 查找:叫号中=', calling?.patientName, '等待=', waiting?.patientName)
const calling = originalQueueList.value.find((i) => i.status === '叫号中') const orgId = calling?.organizationId ?? waiting?.organizationId
const waiting = originalQueueList.value.find((i) => i.status === '等待') console.log('【心内科】handleNextPatient 确定的 orgId=', orgId)
console.log('【心内科】handleNextPatient 查找:叫号中=', calling?.patientName, '等待=', waiting?.patientName)
orgId = calling?.organizationId ?? waiting?.organizationId
console.log('【心内科】handleNextPatient 确定的 orgId=', orgId)
}
if (orgId != null) { if (orgId != null) {
reqData.organizationId = orgId reqData.organizationId = orgId
} }
@@ -1761,13 +1746,9 @@ const handleSkip = async () => {
reqData.id = selectedQueueRow.value.id reqData.id = selectedQueueRow.value.id
reqData.organizationId = selectedQueueRow.value.organizationId reqData.organizationId = selectedQueueRow.value.organizationId
} else { } else {
// 如果没有选中患者,使用查询条件(兼容旧逻辑) // 如果没有选中患者,使用当前叫号中的科室
// 全科模式:优先用”当前叫号中”所在科室 const calling = originalQueueList.value.find((i) => i.status === '叫号中')
let orgId = null const orgId = calling?.organizationId
{
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
orgId = calling?.organizationId
}
if (orgId != null) { if (orgId != null) {
reqData.organizationId = orgId reqData.organizationId = orgId
} }
@@ -1795,13 +1776,9 @@ const handleComplete = async () => {
reqData.id = selectedQueueRow.value.id reqData.id = selectedQueueRow.value.id
reqData.organizationId = selectedQueueRow.value.organizationId reqData.organizationId = selectedQueueRow.value.organizationId
} else { } else {
// 如果没有选中患者,使用查询条件(兼容旧逻辑) // 如果没有选中患者,使用当前叫号中的科室
// 全科模式:优先用”当前叫号中”所在科室 const calling = originalQueueList.value.find((i) => i.status === '叫号中')
let orgId = null const orgId = calling?.organizationId
{
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
orgId = calling?.organizationId
}
if (orgId != null) { if (orgId != null) {
reqData.organizationId = orgId reqData.organizationId = orgId
} }
@@ -1829,13 +1806,9 @@ const handleRequeue = async () => {
reqData.id = selectedQueueRow.value.id reqData.id = selectedQueueRow.value.id
reqData.organizationId = selectedQueueRow.value.organizationId reqData.organizationId = selectedQueueRow.value.organizationId
} else { } else {
// 如果没有选中患者,使用查询条件(兼容旧逻辑) // 如果没有选中患者,使用当前叫号中的科室
// 全科模式:优先用”当前叫号中”所在科室 const calling = originalQueueList.value.find((i) => i.status === '叫号中')
let orgId = null const orgId = calling?.organizationId
{
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
orgId = calling?.organizationId
}
if (orgId != null) { if (orgId != null) {
reqData.organizationId = orgId reqData.organizationId = orgId
} }
@@ -2219,10 +2192,6 @@ onUnmounted(() => {
gap: 10px; gap: 10px;
} }
.queue-actions-right {
display: flex;
gap: 10px;
}
} }
.candidate-actions { .candidate-actions {
@@ -2244,16 +2213,32 @@ onUnmounted(() => {
.filter-section { .filter-section {
margin-bottom: 20px; margin-bottom: 20px;
display: flex;
align-items: flex-start;
justify-content: space-between;
.filter-label { .filter-left {
font-size: 14px; flex: 1;
font-weight: bold;
color: #333; .filter-label {
margin-bottom: 10px; font-size: 14px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.filter-button-wrapper {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
} }
.filter-select-wrapper { .filter-right {
width: 100%; display: flex;
gap: 8px;
flex-shrink: 0;
align-self: flex-end;
} }
} }