Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 85d254990f | |||
| c75b8038ec | |||
| af17d1f460 | |||
| efc1c100aa | |||
|
|
d9c975a950 | ||
| 0874012dae | |||
|
|
cbad13bddc | ||
| a91ee66368 | |||
| 871e2de574 | |||
| 3d279548f0 | |||
| c4a5932a5d | |||
| e9953cd037 |
42
analysis_469.md
Normal file
42
analysis_469.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# 分析报告 — Bug #469
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
检验申请列表的【操作】列仅显示固定的"打印"和"删除"按钮,未根据申请单状态动态切换操作权限。
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
文件 `openhis-ui-vue3/src/views/doctorstation/components/inspection/inspectionApplication.vue` 第97-104行:
|
||||||
|
- 操作列模板中固定渲染"打印"和"删除"按钮,没有任何状态判断逻辑
|
||||||
|
- 缺少"修改"和"撤回"按钮
|
||||||
|
|
||||||
|
## 状态机设计
|
||||||
|
| 状态 | 条件 | 允许的操作 |
|
||||||
|
|------|------|-----------|
|
||||||
|
| 待开立 | applyStatus == 0 | 修改、删除 |
|
||||||
|
| 已开立 | applyStatus == 1 && needExecute != true | 撤回 |
|
||||||
|
| 已执行 | applyStatus == 1 && needExecute == true | 无(仅打印) |
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
1. **前端 Vue**: 操作列改为 `v-if` 条件渲染按钮(修改/删除/撤回/打印)
|
||||||
|
2. **前端 API**: 新增撤回接口 `withdrawInspectionApplication(applyNo)`
|
||||||
|
3. **后端 Controller**: 新增 `POST /withdraw/{applyNo}` 端点
|
||||||
|
4. **后端 Service**: 新增 `withdrawInspectionLabApply` 方法,将 applyStatus 置回 0,needRefund/needExecute 置回 false
|
||||||
|
|
||||||
|
## 修复结果
|
||||||
|
✅ 成功,共14行改动(2个commit完成)
|
||||||
|
|
||||||
|
### 修复详情
|
||||||
|
1. **commit c643a78b** - 初始修复:将操作列从静态"打印/删除"改为基于状态的动态按钮(修改/删除/撤回/详情),10行改动
|
||||||
|
2. **commit f369ea41** - 跟进修复:将"详情"按钮包裹在 `<template v-else>` 中,避免对所有状态始终渲染,4行改动
|
||||||
|
|
||||||
|
### 状态机实现
|
||||||
|
| 状态 | 条件 | 显示按钮 |
|
||||||
|
|------|------|---------|
|
||||||
|
| 待签发 | billStatus == '0' | 修改 + 删除 |
|
||||||
|
| 已签发 | billStatus == '1' | 撤回 |
|
||||||
|
| 其他状态 | 已采证/已送检/报告已出/已作废 | 详情 |
|
||||||
|
|
||||||
|
### 涉及文件
|
||||||
|
- `openhis-ui-vue3/src/views/inpatientDoctor/home/components/applicationShow/testApplication.vue` - 前端操作列动态按钮
|
||||||
|
- `openhis-ui-vue3/src/views/inpatientDoctor/home/components/applicationShow/api.js` - 前端API(deleteRequestForm, withdrawRequestForm)
|
||||||
|
- `openhis-server-new/openhis-application/src/main/java/com/openhis/web/regdoctorstation/controller/RequestFormManageController.java` - 后端Controller(/delete, /withdraw 端点)
|
||||||
|
- `openhis-server-new/openhis-application/src/main/java/com/openhis/web/regdoctorstation/appservice/impl/RequestFormManageAppServiceImpl.java` - 后端Service实现
|
||||||
41
bug547_analysis.md
Normal file
41
bug547_analysis.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Bug #547 分析报告
|
||||||
|
|
||||||
|
## Bug 描述
|
||||||
|
在"系统管理-执行科室配置"页面,选择科室(如检验科)后添加新项目并保存,显示"与未知科室时间冲突"错误。
|
||||||
|
|
||||||
|
## 根因定位
|
||||||
|
|
||||||
|
**核心问题在 `OrganizationLocationAppServiceImpl.java:161-174`**
|
||||||
|
|
||||||
|
时间冲突检测的查询逻辑存在两个缺陷:
|
||||||
|
|
||||||
|
### 缺陷1:查询范围过窄
|
||||||
|
```java
|
||||||
|
// 只查同一科室 + 同一诊疗的记录
|
||||||
|
getOrgLocListByOrgIdAndActivityDefinitionId(orgLoc.getOrganizationId(), orgLoc.getActivityDefinitionId());
|
||||||
|
```
|
||||||
|
只查询**同一科室**的记录。如果同一诊疗项目在其他科室已有配置且时间重叠,不会被当前查询检测到。但系统本应阻止同一诊疗在多个科室同时段执行。
|
||||||
|
|
||||||
|
### 缺陷2:"未知科室"错误提示
|
||||||
|
当冲突记录关联的科室被软删除(`delete_flag='1'`)时,`organizationService.getById()` 受 `@TableLogic` 注解影响查不到该科室,返回 null,错误提示变成"与未知科室时间冲突"。
|
||||||
|
|
||||||
|
数据库验证发现确实存在软删除科室的组织位置记录(内科门诊、上海学校医院、信息科等,共9条)。
|
||||||
|
|
||||||
|
### 数据流
|
||||||
|
|
||||||
|
1. 前端选择科室 → 点击"添加新项目" → 填写诊疗和时间 → 点击"保存"
|
||||||
|
2. 后端 `addOrEditOrgLoc()` 接收请求
|
||||||
|
3. 查询现有冲突记录(**当前只查同科室**)
|
||||||
|
4. 对冲突记录检查时间重叠
|
||||||
|
5. 查找冲突科室名称 → 若科室被软删除则返回 null → "未知科室"
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
1. **修改冲突检测范围**:查询同一 `activityDefinitionId` 的所有记录(跨科室检测),而非仅限当前科室
|
||||||
|
2. **优雅处理"未知科室"**:当 `getById` 返回 null 时,使用 "已删除科室( ID )" 替代 "未知科室",提供更有用的信息
|
||||||
|
3. **新增 Service 方法**:`getOrgLocListByActivityDefinitionId(Long activityDefinitionId)` 用于按诊疗定义查询所有记录
|
||||||
|
|
||||||
|
## 涉及文件
|
||||||
|
- `openhis-server-new/openhis-application/src/main/java/com/openhis/web/basedatamanage/appservice/impl/OrganizationLocationAppServiceImpl.java`
|
||||||
|
- `openhis-server-new/openhis-domain/src/main/java/com/openhis/administration/service/IOrganizationLocationService.java`
|
||||||
|
- `openhis-server-new/openhis-domain/src/main/java/com/openhis/administration/service/impl/OrganizationLocationServiceImpl.java`
|
||||||
2
his-repo
2
his-repo
Submodule his-repo updated: 414c204578...5de8a22418
@@ -4,7 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.core.common.core.domain.R;
|
import com.core.common.core.domain.R;
|
||||||
import com.core.common.utils.SecurityUtils;
|
import com.core.common.utils.SecurityUtils;
|
||||||
import com.openhis.common.constant.CommonConstants;
|
import com.openhis.common.enums.SlotStatus;
|
||||||
import com.openhis.appointmentmanage.domain.DoctorSchedule;
|
import com.openhis.appointmentmanage.domain.DoctorSchedule;
|
||||||
import com.openhis.appointmentmanage.domain.DoctorScheduleWithDateDto;
|
import com.openhis.appointmentmanage.domain.DoctorScheduleWithDateDto;
|
||||||
import com.openhis.appointmentmanage.domain.SchedulePool;
|
import com.openhis.appointmentmanage.domain.SchedulePool;
|
||||||
@@ -502,8 +502,8 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
// 该排班下存在有效患者预约(号源槽:已预约/已锁定/已取号)则禁止删除;已退号、仅可用/已取消槽位不计入
|
// 该排班下存在有效患者预约(号源槽:已预约/已锁定/已取号)则禁止删除;已退号、仅可用/已取消槽位不计入
|
||||||
long appointmentCount = scheduleSlotService.count(new QueryWrapper<ScheduleSlot>()
|
long appointmentCount = scheduleSlotService.count(new QueryWrapper<ScheduleSlot>()
|
||||||
.in("pool_id", poolIds)
|
.in("pool_id", poolIds)
|
||||||
.in("status", CommonConstants.SlotStatus.BOOKED, CommonConstants.SlotStatus.LOCKED,
|
.in("status", SlotStatus.BOOKED.getValue(), SlotStatus.LOCKED.getValue(),
|
||||||
CommonConstants.SlotStatus.CHECKED_IN));
|
SlotStatus.CHECKED_IN.getValue()));
|
||||||
if (appointmentCount > 0) {
|
if (appointmentCount > 0) {
|
||||||
return R.fail("该排班已有患者预约,禁止删除!如需取消请先处理患者退预约或使用'停诊'功能。");
|
return R.fail("该排班已有患者预约,禁止删除!如需取消请先处理患者退预约或使用'停诊'功能。");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import com.openhis.clinical.domain.Ticket;
|
|||||||
import com.openhis.clinical.service.ITicketService;
|
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.enums.SlotStatus;
|
||||||
import com.openhis.common.enums.OrderStatus;
|
import com.openhis.common.enums.OrderStatus;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -193,25 +193,24 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
|||||||
if (Boolean.TRUE.equals(raw.getIsStopped())) {
|
if (Boolean.TRUE.equals(raw.getIsStopped())) {
|
||||||
dto.setStatus("已停诊");
|
dto.setStatus("已停诊");
|
||||||
} else {
|
} else {
|
||||||
Integer slotStatus = raw.getSlotStatus();
|
SlotStatus status = SlotStatus.getByValue(raw.getSlotStatus());
|
||||||
if (slotStatus != null) {
|
if (status != null) {
|
||||||
if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
|
if (status == SlotStatus.LOCKED) {
|
||||||
dto.setStatus("已取号");
|
|
||||||
} else if (SlotStatus.BOOKED.equals(slotStatus)) {
|
|
||||||
// order_main.status: 0=患者取消(已退号) 2=系统取消 其余=已预约
|
|
||||||
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
||||||
dto.setStatus("已退号");
|
dto.setStatus("已退号");
|
||||||
} else if (OrderStatus.SYSTEM_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
|
||||||
dto.setStatus("系统取消");
|
|
||||||
} else {
|
} else {
|
||||||
dto.setStatus("已预约");
|
dto.setStatus("已锁定");
|
||||||
}
|
}
|
||||||
} else if (SlotStatus.RETURNED.equals(slotStatus)) {
|
} else if (status == SlotStatus.BOOKED) {
|
||||||
dto.setStatus("已退号");
|
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
||||||
} else if (SlotStatus.CANCELLED.equals(slotStatus)) {
|
dto.setStatus("已退号");
|
||||||
|
} else {
|
||||||
|
dto.setStatus("已取号");
|
||||||
|
}
|
||||||
|
} else if (status == SlotStatus.CANCELLED) {
|
||||||
dto.setStatus("已停诊");
|
dto.setStatus("已停诊");
|
||||||
} else if (SlotStatus.LOCKED.equals(slotStatus)) {
|
} else if (status == SlotStatus.RETURNED) {
|
||||||
dto.setStatus("已锁定");
|
dto.setStatus("已退号");
|
||||||
} else {
|
} else {
|
||||||
dto.setStatus("未预约");
|
dto.setStatus("未预约");
|
||||||
}
|
}
|
||||||
@@ -237,6 +236,10 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
|||||||
/**
|
/**
|
||||||
* 统一状态入参,避免前端状态值大小写/中文/数字差异导致 SQL 条件失效后回全量数据
|
* 统一状态入参,避免前端状态值大小写/中文/数字差异导致 SQL 条件失效后回全量数据
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* 规范前端传入的状态查询参数,映射到 SQL 的 slotStatusNormExpr 值。
|
||||||
|
* 数值映射: 0=待约 1=已约(签到后) 2=锁定(预约后) 3=已签到 4=已停诊 5=已退号
|
||||||
|
*/
|
||||||
private void normalizeQueryStatus(com.openhis.appointmentmanage.dto.TicketQueryDTO query) {
|
private void normalizeQueryStatus(com.openhis.appointmentmanage.dto.TicketQueryDTO query) {
|
||||||
String rawStatus = query.getStatus();
|
String rawStatus = query.getStatus();
|
||||||
if (rawStatus == null) {
|
if (rawStatus == null) {
|
||||||
@@ -263,28 +266,31 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
|||||||
case "已预约":
|
case "已预约":
|
||||||
query.setStatus("booked");
|
query.setStatus("booked");
|
||||||
break;
|
break;
|
||||||
|
case "locked":
|
||||||
|
case "2":
|
||||||
|
case "已锁定":
|
||||||
|
query.setStatus("locked");
|
||||||
|
break;
|
||||||
case "checked":
|
case "checked":
|
||||||
case "checkin":
|
case "checkin":
|
||||||
case "checkedin":
|
case "checkedin":
|
||||||
case "2":
|
case "3":
|
||||||
case "已取号":
|
case "已取号":
|
||||||
query.setStatus("checked");
|
query.setStatus("checked");
|
||||||
break;
|
break;
|
||||||
case "cancelled":
|
case "cancelled":
|
||||||
case "canceled":
|
case "canceled":
|
||||||
case "3":
|
case "4":
|
||||||
case "已停诊":
|
case "已停诊":
|
||||||
case "已取消":
|
case "已取消":
|
||||||
query.setStatus("cancelled");
|
query.setStatus("cancelled");
|
||||||
break;
|
break;
|
||||||
case "returned":
|
case "returned":
|
||||||
case "4":
|
|
||||||
case "5":
|
case "5":
|
||||||
case "已退号":
|
case "已退号":
|
||||||
query.setStatus("returned");
|
query.setStatus("returned");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// 设置为 impossible 值,配合 mapper 的 otherwise 分支直接返回空
|
|
||||||
query.setStatus("__invalid__");
|
query.setStatus("__invalid__");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -367,26 +373,25 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
|||||||
if (Boolean.TRUE.equals(raw.getIsStopped())) {
|
if (Boolean.TRUE.equals(raw.getIsStopped())) {
|
||||||
dto.setStatus("已停诊");
|
dto.setStatus("已停诊");
|
||||||
} else {
|
} else {
|
||||||
// 第二关:看独立的细分槽位状态 (0: 可用, 1: 已预约, 2: 已取消...)
|
// 第二关:看独立的细分槽位状态 (0: 可用, 1: 已预约, 2: 已锁定...)
|
||||||
Integer slotStatus = raw.getSlotStatus();
|
SlotStatus status = SlotStatus.getByValue(raw.getSlotStatus());
|
||||||
if (slotStatus != null) {
|
if (status != null) {
|
||||||
if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
|
if (status == SlotStatus.LOCKED) {
|
||||||
dto.setStatus("已取号");
|
|
||||||
} else if (SlotStatus.BOOKED.equals(slotStatus)) {
|
|
||||||
// order_main.status: 0=患者取消(已退号) 2=系统取消 其余=已预约
|
|
||||||
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
||||||
dto.setStatus("已退号");
|
dto.setStatus("已退号");
|
||||||
} else if (OrderStatus.SYSTEM_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
|
||||||
dto.setStatus("系统取消");
|
|
||||||
} else {
|
} else {
|
||||||
dto.setStatus("已预约");
|
dto.setStatus("已锁定");
|
||||||
}
|
}
|
||||||
} else if (SlotStatus.RETURNED.equals(slotStatus)) {
|
} else if (status == SlotStatus.BOOKED) {
|
||||||
dto.setStatus("已退号");
|
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
||||||
} else if (SlotStatus.CANCELLED.equals(slotStatus)) {
|
dto.setStatus("已退号");
|
||||||
|
} else {
|
||||||
|
dto.setStatus("已取号");
|
||||||
|
}
|
||||||
|
} else if (status == SlotStatus.CANCELLED) {
|
||||||
dto.setStatus("已停诊");
|
dto.setStatus("已停诊");
|
||||||
} else if (SlotStatus.LOCKED.equals(slotStatus)) {
|
} else if (status == SlotStatus.RETURNED) {
|
||||||
dto.setStatus("已锁定");
|
dto.setStatus("已退号");
|
||||||
} else {
|
} else {
|
||||||
dto.setStatus("未预约");
|
dto.setStatus("未预约");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ public class OrganizationLocationAppServiceImpl implements IOrganizationLocation
|
|||||||
String activityName = activityDef != null ? activityDef.getName() : "";
|
String activityName = activityDef != null ? activityDef.getName() : "";
|
||||||
|
|
||||||
List<OrganizationLocation> organizationLocationList =
|
List<OrganizationLocation> organizationLocationList =
|
||||||
organizationLocationService.getOrgLocListByOrgIdAndActivityDefinitionId(orgLoc.getOrganizationId(), orgLoc.getActivityDefinitionId());
|
organizationLocationService.getOrgLocListByActivityDefinitionId(orgLoc.getActivityDefinitionId());
|
||||||
organizationLocationList = (orgLoc.getId() != null)
|
organizationLocationList = (orgLoc.getId() != null)
|
||||||
? organizationLocationList.stream().filter(item -> !orgLoc.getId().equals(item.getId())).toList()
|
? organizationLocationList.stream().filter(item -> !orgLoc.getId().equals(item.getId())).toList()
|
||||||
: organizationLocationList;
|
: organizationLocationList;
|
||||||
@@ -169,7 +169,7 @@ public class OrganizationLocationAppServiceImpl implements IOrganizationLocation
|
|||||||
if (DateTimeUtils.isOverlap(organizationLocation.getStartTime(), organizationLocation.getEndTime(),
|
if (DateTimeUtils.isOverlap(organizationLocation.getStartTime(), organizationLocation.getEndTime(),
|
||||||
orgLoc.getStartTime(), orgLoc.getEndTime())) {
|
orgLoc.getStartTime(), orgLoc.getEndTime())) {
|
||||||
Organization org = organizationService.getById(organizationLocation.getOrganizationId());
|
Organization org = organizationService.getById(organizationLocation.getOrganizationId());
|
||||||
String organizationName = org != null ? org.getName() : "未知科室";
|
String organizationName = org != null ? org.getName() : "已删除科室(ID:" + organizationLocation.getOrganizationId() + ")";
|
||||||
return R.fail("当前诊疗:" + activityName + CommonConstants.Common.DASH + orgLoc.getStartTime()
|
return R.fail("当前诊疗:" + activityName + CommonConstants.Common.DASH + orgLoc.getStartTime()
|
||||||
+ CommonConstants.Common.DASH + orgLoc.getEndTime() + "与" + organizationName + "时间冲突");
|
+ CommonConstants.Common.DASH + orgLoc.getEndTime() + "与" + organizationName + "时间冲突");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import com.openhis.administration.mapper.PatientMapper;
|
|||||||
import com.openhis.administration.service.*;
|
import com.openhis.administration.service.*;
|
||||||
import com.openhis.common.constant.CommonConstants;
|
import com.openhis.common.constant.CommonConstants;
|
||||||
import com.openhis.common.constant.PromptMsgConstant;
|
import com.openhis.common.constant.PromptMsgConstant;
|
||||||
|
import com.openhis.common.enums.SlotStatus;
|
||||||
import com.openhis.common.enums.*;
|
import com.openhis.common.enums.*;
|
||||||
import com.openhis.common.enums.ybenums.YbPayment;
|
import com.openhis.common.enums.ybenums.YbPayment;
|
||||||
import com.openhis.common.utils.EnumUtils;
|
import com.openhis.common.utils.EnumUtils;
|
||||||
@@ -643,8 +644,7 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
|||||||
.set(Order::getStatus, OrderStatus.PATIENT_CANCELLED.getValue())
|
.set(Order::getStatus, OrderStatus.PATIENT_CANCELLED.getValue())
|
||||||
.set(Order::getPayStatus, PaymentStatus.REFUND_ALL.getValue())
|
.set(Order::getPayStatus, PaymentStatus.REFUND_ALL.getValue())
|
||||||
.set(Order::getCancelTime, new Date())
|
.set(Order::getCancelTime, new Date())
|
||||||
.set(Order::getCancelReason,
|
.set(Order::getCancelReason, "诊前退号")
|
||||||
StringUtils.isNotEmpty(reason) ? reason : "诊前退号")
|
|
||||||
.set(Order::getUpdateTime, new Date())
|
.set(Order::getUpdateTime, new Date())
|
||||||
.setSql("version = version + 1")
|
.setSql("version = version + 1")
|
||||||
.eq(Order::getId, appointmentOrder.getId())
|
.eq(Order::getId, appointmentOrder.getId())
|
||||||
@@ -660,17 +660,27 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
|||||||
return appointmentOrder.getId();
|
return appointmentOrder.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
int slotRows = scheduleSlotMapper.updateSlotStatus(slotId, CommonConstants.SlotStatus.AVAILABLE);
|
// 只有已预约(1)的号源才能退号,对应签到后的 BOOKED 状态
|
||||||
if (slotRows > 0) {
|
ScheduleSlot slot = scheduleSlotMapper.selectById(slotId);
|
||||||
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
|
if (slot == null || !SlotStatus.BOOKED.getValue().equals(slot.getStatus())) {
|
||||||
if (poolId != null) {
|
log.warn("退号跳过:槽位非已预约状态, slotId={}, status={}", slotId,
|
||||||
schedulePoolMapper.refreshPoolStats(poolId);
|
slot != null ? slot.getStatus() : null);
|
||||||
schedulePoolMapper.update(null,
|
return appointmentOrder.getId();
|
||||||
new LambdaUpdateWrapper<SchedulePool>()
|
}
|
||||||
.setSql("version = version + 1")
|
|
||||||
.set(SchedulePool::getUpdateTime, new Date())
|
int slotRows = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.AVAILABLE.getValue());
|
||||||
.eq(SchedulePool::getId, poolId));
|
if (slotRows == 0) {
|
||||||
}
|
log.warn("退号时更新槽位状态未影响任何行, slotId={}", slotId);
|
||||||
|
return appointmentOrder.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
|
||||||
|
if (poolId != null) {
|
||||||
|
schedulePoolMapper.update(null,
|
||||||
|
new LambdaUpdateWrapper<SchedulePool>()
|
||||||
|
.setSql("booked_num = booked_num - 1, version = version + 1")
|
||||||
|
.set(SchedulePool::getUpdateTime, new Date())
|
||||||
|
.eq(SchedulePool::getId, poolId));
|
||||||
}
|
}
|
||||||
return appointmentOrder.getId();
|
return appointmentOrder.getId();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ public class PatientInformationServiceImpl implements IPatientInformationService
|
|||||||
// log.debug("添加病人信息,patientInfoDto:{}", patientBaseInfoDto);
|
// log.debug("添加病人信息,patientInfoDto:{}", patientBaseInfoDto);
|
||||||
// 如果患者没有输入身份证号则根据年龄自动生成
|
// 如果患者没有输入身份证号则根据年龄自动生成
|
||||||
String idCard = patientBaseInfoDto.getIdCard();
|
String idCard = patientBaseInfoDto.getIdCard();
|
||||||
if (idCard == null || CommonConstants.Common.AREA_CODE.equals(idCard.substring(0, 6))) {
|
if (idCard == null || idCard.length() < 6 || CommonConstants.Common.AREA_CODE.equals(idCard.substring(0, 6))) {
|
||||||
if (patientBaseInfoDto.getAge() != null) {
|
if (patientBaseInfoDto.getAge() != null) {
|
||||||
idCard = IdCardUtil.generateIdByAge(patientBaseInfoDto.getAge());
|
idCard = IdCardUtil.generateIdByAge(patientBaseInfoDto.getAge());
|
||||||
patientBaseInfoDto.setIdCard(idCard);
|
patientBaseInfoDto.setIdCard(idCard);
|
||||||
|
|||||||
@@ -768,36 +768,4 @@ public class CommonConstants {
|
|||||||
Integer ACCOUNT_DEVICE_TYPE = 6;
|
Integer ACCOUNT_DEVICE_TYPE = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 号源槽位状态 (adm_schedule_slot.status)
|
|
||||||
*/
|
|
||||||
public interface SlotStatus {
|
|
||||||
/** 可用 / 待预约 */
|
|
||||||
Integer AVAILABLE = 0;
|
|
||||||
/** 已预约 */
|
|
||||||
Integer BOOKED = 1;
|
|
||||||
/** 已取消 / 已停诊 */
|
|
||||||
Integer CANCELLED = 2;
|
|
||||||
/** 已签到 / 已取号 */
|
|
||||||
Integer CHECKED_IN = 3;
|
|
||||||
/** 已锁定 */
|
|
||||||
Integer LOCKED = 4;
|
|
||||||
/** 已退号 */
|
|
||||||
Integer RETURNED = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 预约订单状态 (order_main.status)
|
|
||||||
*/
|
|
||||||
public interface AppointmentOrderStatus {
|
|
||||||
/** 已预约 (待就诊) */
|
|
||||||
Integer BOOKED = 1;
|
|
||||||
/** 已取号 (已就诊) */
|
|
||||||
Integer CHECKED_IN = 2;
|
|
||||||
/** 已取消 */
|
|
||||||
Integer CANCELLED = 3;
|
|
||||||
/** 已退号 */
|
|
||||||
Integer RETURNED = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.openhis.common.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 号源槽位状态 (adm_schedule_slot.status)
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 状态流转:
|
||||||
|
* 预约 → 0→2 (锁定), locked_num+1
|
||||||
|
* 取消预约 → 2→0 (释放), locked_num-1
|
||||||
|
* 签到 → 2→1 (已约), locked_num-1, booked_num+1
|
||||||
|
* 退号 → 1→0 (释放), booked_num-1
|
||||||
|
* 停诊 → 任意→4 (已取消)
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum SlotStatus implements HisEnumInterface {
|
||||||
|
|
||||||
|
/** 可用 / 待预约 */
|
||||||
|
AVAILABLE(0, "available", "可用"),
|
||||||
|
|
||||||
|
/** 已预约 */
|
||||||
|
BOOKED(1, "booked", "已预约"),
|
||||||
|
|
||||||
|
/** 已锁定 (约而不付:预约后锁定号源) */
|
||||||
|
LOCKED(2, "locked", "已锁定"),
|
||||||
|
|
||||||
|
/** 已签到 / 已取号 */
|
||||||
|
CHECKED_IN(3, "checked_in", "已签到"),
|
||||||
|
|
||||||
|
/** 已取消 / 已停诊 */
|
||||||
|
CANCELLED(4, "cancelled", "已取消"),
|
||||||
|
|
||||||
|
/** 已退号 */
|
||||||
|
RETURNED(5, "returned", "已退号");
|
||||||
|
|
||||||
|
private final Integer value;
|
||||||
|
private final String code;
|
||||||
|
private final String info;
|
||||||
|
|
||||||
|
public static SlotStatus getByValue(Integer value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (SlotStatus val : values()) {
|
||||||
|
if (val.getValue().equals(value)) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,4 +38,12 @@ public interface IOrganizationLocationService extends IService<OrganizationLocat
|
|||||||
*/
|
*/
|
||||||
List<OrganizationLocation> getOrgLocListByOrgIdAndActivityDefinitionId(Long organizationId, Long activityDefinitionId);
|
List<OrganizationLocation> getOrgLocListByOrgIdAndActivityDefinitionId(Long organizationId, Long activityDefinitionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据诊疗定义id查询所有执行科室列表(跨科室)
|
||||||
|
*
|
||||||
|
* @param activityDefinitionId 诊疗定义id
|
||||||
|
* @return 执行科室列表
|
||||||
|
*/
|
||||||
|
List<OrganizationLocation> getOrgLocListByActivityDefinitionId(Long activityDefinitionId);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -64,4 +64,16 @@ public class OrganizationLocationServiceImpl extends ServiceImpl<OrganizationLoc
|
|||||||
.eq(OrganizationLocation::getActivityDefinitionId, activityDefinitionId));
|
.eq(OrganizationLocation::getActivityDefinitionId, activityDefinitionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据诊疗定义id查询所有执行科室列表(跨科室)
|
||||||
|
*
|
||||||
|
* @param activityDefinitionId 诊疗定义id
|
||||||
|
* @return 执行科室列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<OrganizationLocation> getOrgLocListByActivityDefinitionId(Long activityDefinitionId) {
|
||||||
|
return baseMapper.selectList(new LambdaQueryWrapper<OrganizationLocation>()
|
||||||
|
.eq(OrganizationLocation::getActivityDefinitionId, activityDefinitionId));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -10,10 +10,11 @@ import org.springframework.stereotype.Repository;
|
|||||||
public interface SchedulePoolMapper extends BaseMapper<SchedulePool> {
|
public interface SchedulePoolMapper extends BaseMapper<SchedulePool> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按号源池实时重算统计值,避免并发场景下计数漂移。
|
* 按号源池实时重算统计值。
|
||||||
*
|
*
|
||||||
* 说明:available_num 在当前项目中可能为数据库生成列,因此这里仅维护
|
* @param poolId 号源池ID
|
||||||
* booked_num / locked_num,剩余号由数据库或查询逻辑计算。
|
* @param bookedStatus 已约状态值,由 SlotStatus.BOOKED.getValue() 传入
|
||||||
|
* @param lockedStatus 锁定状态值,由 SlotStatus.LOCKED.getValue() 传入
|
||||||
*/
|
*/
|
||||||
@Update("""
|
@Update("""
|
||||||
UPDATE adm_schedule_pool p
|
UPDATE adm_schedule_pool p
|
||||||
@@ -23,20 +24,22 @@ public interface SchedulePoolMapper extends BaseMapper<SchedulePool> {
|
|||||||
FROM adm_schedule_slot s
|
FROM adm_schedule_slot s
|
||||||
WHERE s.pool_id = p.id
|
WHERE s.pool_id = p.id
|
||||||
AND s.delete_flag = '0'
|
AND s.delete_flag = '0'
|
||||||
AND s.status = 1
|
AND s.status = #{bookedStatus}
|
||||||
), 0),
|
), 0),
|
||||||
locked_num = COALESCE((
|
locked_num = COALESCE((
|
||||||
SELECT COUNT(1)
|
SELECT COUNT(1)
|
||||||
FROM adm_schedule_slot s
|
FROM adm_schedule_slot s
|
||||||
WHERE s.pool_id = p.id
|
WHERE s.pool_id = p.id
|
||||||
AND s.delete_flag = '0'
|
AND s.delete_flag = '0'
|
||||||
AND s.status = 3
|
AND s.status = #{lockedStatus}
|
||||||
), 0),
|
), 0),
|
||||||
update_time = now()
|
update_time = now()
|
||||||
WHERE p.id = #{poolId}
|
WHERE p.id = #{poolId}
|
||||||
AND p.delete_flag = '0'
|
AND p.delete_flag = '0'
|
||||||
""")
|
""")
|
||||||
int refreshPoolStats(@Param("poolId") Long poolId);
|
int refreshPoolStats(@Param("poolId") Long poolId,
|
||||||
|
@Param("bookedStatus") Integer bookedStatus,
|
||||||
|
@Param("lockedStatus") Integer lockedStatus);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 签到时更新号源池统计:锁定数-1,已预约数+1
|
* 签到时更新号源池统计:锁定数-1,已预约数+1
|
||||||
|
|||||||
@@ -22,9 +22,12 @@ public interface ScheduleSlotMapper extends BaseMapper<ScheduleSlot> {
|
|||||||
TicketSlotDTO selectTicketSlotById(@Param("id") Long id);
|
TicketSlotDTO selectTicketSlotById(@Param("id") Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 原子抢占槽位:仅当当前状态=0(可用)时,更新为1(已预约)。
|
* 原子抢占槽位:仅当当前状态=0(待约)时,更新为目标锁定状态。
|
||||||
|
*
|
||||||
|
* @param slotId 槽位ID
|
||||||
|
* @param lockedStatus 锁定状态值,由 SlotStatus.LOCKED.getValue() 传入
|
||||||
*/
|
*/
|
||||||
int lockSlotForBooking(@Param("slotId") Long slotId);
|
int lockSlotForBooking(@Param("slotId") Long slotId, @Param("lockedStatus") Integer lockedStatus);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按主键更新槽位状态。
|
* 按主键更新槽位状态。
|
||||||
@@ -34,12 +37,16 @@ public interface ScheduleSlotMapper extends BaseMapper<ScheduleSlot> {
|
|||||||
/**
|
/**
|
||||||
* 更新槽位状态并记录签到时间
|
* 更新槽位状态并记录签到时间
|
||||||
*
|
*
|
||||||
* @param slotId 槽位ID
|
* @param slotId 槽位ID
|
||||||
* @param status 状态
|
* @param status 目标状态,由 SlotStatus.BOOKED.getValue() 传入
|
||||||
* @param checkInTime 签到时间
|
* @param checkInTime 签到时间
|
||||||
|
* @param requiredStatus 前置状态,由 SlotStatus.LOCKED.getValue() 传入
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
int updateSlotStatusAndCheckInTime(@Param("slotId") Long slotId, @Param("status") Integer status, @Param("checkInTime") Date checkInTime);
|
int updateSlotStatusAndCheckInTime(@Param("slotId") Long slotId,
|
||||||
|
@Param("status") Integer status,
|
||||||
|
@Param("checkInTime") Date checkInTime,
|
||||||
|
@Param("requiredStatus") Integer requiredStatus);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据槽位ID查询所属号源池ID。
|
* 根据槽位ID查询所属号源池ID。
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.openhis.clinical.service.impl;
|
package com.openhis.clinical.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.openhis.appointmentmanage.domain.AppointmentConfig;
|
import com.openhis.appointmentmanage.domain.AppointmentConfig;
|
||||||
import com.openhis.appointmentmanage.service.IAppointmentConfigService;
|
import com.openhis.appointmentmanage.service.IAppointmentConfigService;
|
||||||
import com.openhis.appointmentmanage.domain.TicketSlotDTO;
|
import com.openhis.appointmentmanage.domain.TicketSlotDTO;
|
||||||
|
import com.openhis.appointmentmanage.domain.SchedulePool;
|
||||||
import com.openhis.appointmentmanage.domain.ScheduleSlot;
|
import com.openhis.appointmentmanage.domain.ScheduleSlot;
|
||||||
import com.openhis.appointmentmanage.mapper.SchedulePoolMapper;
|
import com.openhis.appointmentmanage.mapper.SchedulePoolMapper;
|
||||||
import com.openhis.appointmentmanage.mapper.ScheduleSlotMapper;
|
import com.openhis.appointmentmanage.mapper.ScheduleSlotMapper;
|
||||||
@@ -13,7 +15,7 @@ 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.SlotStatus;
|
import com.openhis.common.enums.SlotStatus;
|
||||||
import com.openhis.common.enums.OrderStatus;
|
import com.openhis.common.enums.OrderStatus;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -177,7 +179,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
logger.error("安全拦截:号源底库核对失败,slotId: {}", slotId);
|
logger.error("安全拦截:号源底库核对失败,slotId: {}", slotId);
|
||||||
throw new RuntimeException("号源数据不存在");
|
throw new RuntimeException("号源数据不存在");
|
||||||
}
|
}
|
||||||
if (slot.getSlotStatus() != null && !SlotStatus.AVAILABLE.equals(slot.getSlotStatus())) {
|
if (slot.getSlotStatus() != null && SlotStatus.getByValue(slot.getSlotStatus()) != SlotStatus.AVAILABLE) {
|
||||||
throw new RuntimeException("手慢了!该号源已刚刚被他人抢占");
|
throw new RuntimeException("手慢了!该号源已刚刚被他人抢占");
|
||||||
}
|
}
|
||||||
if (Boolean.TRUE.equals(slot.getIsStopped())) {
|
if (Boolean.TRUE.equals(slot.getIsStopped())) {
|
||||||
@@ -205,7 +207,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 原子抢占:避免并发下同一槽位被重复预约
|
// 原子抢占:避免并发下同一槽位被重复预约
|
||||||
int lockRows = scheduleSlotMapper.lockSlotForBooking(slotId);
|
int lockRows = scheduleSlotMapper.lockSlotForBooking(slotId, SlotStatus.LOCKED.getValue());
|
||||||
if (lockRows <= 0) {
|
if (lockRows <= 0) {
|
||||||
throw new RuntimeException("手慢了!该号源已刚刚被他人抢占");
|
throw new RuntimeException("手慢了!该号源已刚刚被他人抢占");
|
||||||
}
|
}
|
||||||
@@ -260,7 +262,15 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
throw new RuntimeException("预约成功但号源回填订单失败,请重试");
|
throw new RuntimeException("预约成功但号源回填订单失败,请重试");
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshPoolStatsBySlotId(slotId);
|
// 6. 预约成功后 locked_num+1(原子递增替代全量 recount,避免并发计数漂移)
|
||||||
|
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
|
||||||
|
if (poolId != null) {
|
||||||
|
schedulePoolMapper.update(null,
|
||||||
|
new LambdaUpdateWrapper<SchedulePool>()
|
||||||
|
.setSql("locked_num = locked_num + 1, version = version + 1")
|
||||||
|
.set(SchedulePool::getUpdateTime, new Date())
|
||||||
|
.eq(SchedulePool::getId, poolId));
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +287,8 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
if (slot == null) {
|
if (slot == null) {
|
||||||
throw new RuntimeException("号源槽位不存在");
|
throw new RuntimeException("号源槽位不存在");
|
||||||
}
|
}
|
||||||
if (slot.getSlotStatus() == null || !SlotStatus.BOOKED.equals(slot.getSlotStatus())) {
|
// 只有锁定态(2)的号源可以取消预约
|
||||||
|
if (slot.getSlotStatus() == null || SlotStatus.getByValue(slot.getSlotStatus()) != SlotStatus.LOCKED) {
|
||||||
throw new RuntimeException("号源不可取消预约");
|
throw new RuntimeException("号源不可取消预约");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +303,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
orderService.cancelAppointmentOrder(order.getId(), "患者取消预约");
|
orderService.cancelAppointmentOrder(order.getId(), "患者取消预约");
|
||||||
}
|
}
|
||||||
|
|
||||||
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.AVAILABLE);
|
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.AVAILABLE.getValue());
|
||||||
if (updated > 0) {
|
if (updated > 0) {
|
||||||
refreshPoolStatsBySlotId(slotId);
|
refreshPoolStatsBySlotId(slotId);
|
||||||
}
|
}
|
||||||
@@ -318,11 +329,14 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
orderService.updateOrderStatusById(latestOrder.getId(), OrderStatus.ACTIVE.getValue());
|
orderService.updateOrderStatusById(latestOrder.getId(), OrderStatus.ACTIVE.getValue());
|
||||||
orderMapper.updatePayStatus(latestOrder.getId(), 1, new Date());
|
orderMapper.updatePayStatus(latestOrder.getId(), 1, new Date());
|
||||||
|
|
||||||
// 2. 查询号源槽位信息
|
// 2. 只有锁定态(2)的号源才能签到,签到时 2→1(LOCKED→BOOKED)
|
||||||
ScheduleSlot slot = scheduleSlotMapper.selectById(slotId);
|
ScheduleSlot slot = scheduleSlotMapper.selectById(slotId);
|
||||||
|
if (slot == null || !SlotStatus.LOCKED.getValue().equals(slot.getStatus())) {
|
||||||
|
throw new RuntimeException("号源状态异常,无法签到");
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 更新号源槽位状态为已签到,记录签到时间
|
// 3. 更新号源槽位状态 2→1(LOCKED→BOOKED,已预约=已签到)
|
||||||
scheduleSlotMapper.updateSlotStatusAndCheckInTime(slotId, SlotStatus.CHECKED_IN, new Date());
|
scheduleSlotMapper.updateSlotStatusAndCheckInTime(slotId, SlotStatus.BOOKED.getValue(), new Date(), SlotStatus.LOCKED.getValue());
|
||||||
|
|
||||||
// 4. 更新号源池统计:锁定数-1,已预约数+1
|
// 4. 更新号源池统计:锁定数-1,已预约数+1
|
||||||
if (slot != null && slot.getPoolId() != null) {
|
if (slot != null && slot.getPoolId() != null) {
|
||||||
@@ -351,7 +365,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
orderService.cancelAppointmentOrder(order.getId(), "医生停诊");
|
orderService.cancelAppointmentOrder(order.getId(), "医生停诊");
|
||||||
}
|
}
|
||||||
|
|
||||||
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.CANCELLED);
|
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.CANCELLED.getValue());
|
||||||
if (updated > 0) {
|
if (updated > 0) {
|
||||||
refreshPoolStatsBySlotId(slotId);
|
refreshPoolStatsBySlotId(slotId);
|
||||||
}
|
}
|
||||||
@@ -364,7 +378,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
private void refreshPoolStatsBySlotId(Long slotId) {
|
private void refreshPoolStatsBySlotId(Long slotId) {
|
||||||
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
|
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
|
||||||
if (poolId != null) {
|
if (poolId != null) {
|
||||||
schedulePoolMapper.refreshPoolStats(poolId);
|
schedulePoolMapper.refreshPoolStats(poolId, SlotStatus.BOOKED.getValue(), SlotStatus.LOCKED.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,17 @@
|
|||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="com.openhis.appointmentmanage.mapper.ScheduleSlotMapper">
|
<mapper namespace="com.openhis.appointmentmanage.mapper.ScheduleSlotMapper">
|
||||||
|
|
||||||
<!-- 统一状态值(兼容数字/英文字符串存储),输出 Integer,避免 resultType 映射 NumberFormatException -->
|
<!--
|
||||||
|
统一状态值映射: DB 数值 → 规范化输出
|
||||||
|
0=待约 1=已约(签到后) 2=锁定(预约后) 3=已签到 4=已停诊 5=已退号
|
||||||
|
-->
|
||||||
<sql id="slotStatusNormExpr">
|
<sql id="slotStatusNormExpr">
|
||||||
CASE
|
CASE
|
||||||
WHEN LOWER(CONCAT('', s.status)) IN ('0', 'unbooked', 'available') THEN 0
|
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 ('1', 'booked') THEN 1
|
||||||
WHEN LOWER(CONCAT('', s.status)) IN ('2', 'cancelled', 'canceled', 'stopped') THEN 2
|
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 ('3', 'checked', 'checked_in', 'checkin') THEN 3
|
||||||
WHEN LOWER(CONCAT('', s.status)) IN ('4', 'locked') THEN 4
|
WHEN LOWER(CONCAT('', s.status)) IN ('4', 'cancelled', 'canceled', 'stopped') THEN 4
|
||||||
WHEN LOWER(CONCAT('', s.status)) IN ('5', 'returned') THEN 5
|
WHEN LOWER(CONCAT('', s.status)) IN ('5', 'returned') THEN 5
|
||||||
ELSE NULL
|
ELSE NULL
|
||||||
END
|
END
|
||||||
@@ -31,9 +34,9 @@
|
|||||||
CASE
|
CASE
|
||||||
WHEN LOWER(CONCAT('', p.status)) IN ('0', 'unbooked', 'available') THEN 0
|
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 ('1', 'booked') THEN 1
|
||||||
WHEN LOWER(CONCAT('', p.status)) IN ('2', 'cancelled', 'canceled', 'stopped') THEN 2
|
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 ('3', 'checked', 'checked_in', 'checkin') THEN 3
|
||||||
WHEN LOWER(CONCAT('', p.status)) IN ('4', 'locked') THEN 4
|
WHEN LOWER(CONCAT('', p.status)) IN ('4', 'cancelled', 'canceled', 'stopped') THEN 4
|
||||||
WHEN LOWER(CONCAT('', p.status)) IN ('5', 'returned') THEN 5
|
WHEN LOWER(CONCAT('', p.status)) IN ('5', 'returned') THEN 5
|
||||||
ELSE NULL
|
ELSE NULL
|
||||||
END
|
END
|
||||||
@@ -149,10 +152,11 @@
|
|||||||
s.id = #{id}
|
s.id = #{id}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 预约锁定: 0→#{lockedStatus} (AVAILABLE→LOCKED),由枚举传入 -->
|
||||||
<update id="lockSlotForBooking">
|
<update id="lockSlotForBooking">
|
||||||
UPDATE adm_schedule_slot
|
UPDATE adm_schedule_slot
|
||||||
SET
|
SET
|
||||||
status = 1,
|
status = #{lockedStatus},
|
||||||
update_time = now()
|
update_time = now()
|
||||||
WHERE
|
WHERE
|
||||||
id = #{slotId}
|
id = #{slotId}
|
||||||
@@ -174,6 +178,7 @@
|
|||||||
AND delete_flag = '0'
|
AND delete_flag = '0'
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<!-- 签到: #{requiredStatus}→#{status} (LOCKED→BOOKED),前置条件由枚举传入 -->
|
||||||
<update id="updateSlotStatusAndCheckInTime">
|
<update id="updateSlotStatusAndCheckInTime">
|
||||||
UPDATE adm_schedule_slot
|
UPDATE adm_schedule_slot
|
||||||
SET
|
SET
|
||||||
@@ -182,6 +187,7 @@
|
|||||||
update_time = NOW()
|
update_time = NOW()
|
||||||
WHERE
|
WHERE
|
||||||
id = #{slotId}
|
id = #{slotId}
|
||||||
|
AND status = #{requiredStatus}
|
||||||
AND delete_flag = '0'
|
AND delete_flag = '0'
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
@@ -202,7 +208,7 @@
|
|||||||
update_time = now()
|
update_time = now()
|
||||||
WHERE
|
WHERE
|
||||||
id = #{slotId}
|
id = #{slotId}
|
||||||
AND status = 1
|
AND status = 2
|
||||||
AND delete_flag = '0'
|
AND delete_flag = '0'
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
@@ -299,15 +305,16 @@
|
|||||||
<if test="query.phone != null and query.phone != ''">
|
<if test="query.phone != null and query.phone != ''">
|
||||||
AND o.phone LIKE CONCAT('%', #{query.phone}, '%')
|
AND o.phone LIKE CONCAT('%', #{query.phone}, '%')
|
||||||
</if>
|
</if>
|
||||||
<!-- 5. 按系统时间过滤(Bug #398 #399 修复:仅未预约受时间过滤,已预约/已取号/已退号不受影响) -->
|
<!-- 5. 时间过滤: 仅待约(0)受时间限制,已锁定(2)/已约(1)/已签到(3)/已退号(5)不受影响 -->
|
||||||
AND (
|
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())))
|
(<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" /> = 1
|
||||||
|
OR <include refid="slotStatusNormExpr" /> = 2
|
||||||
OR <include refid="slotStatusNormExpr" /> = 3
|
OR <include refid="slotStatusNormExpr" /> = 3
|
||||||
OR <include refid="slotStatusNormExpr" /> = 5
|
OR <include refid="slotStatusNormExpr" /> = 5
|
||||||
OR <include refid="orderStatusNormExpr" /> = 4
|
OR <include refid="orderStatusNormExpr" /> = 4
|
||||||
)
|
)
|
||||||
<!-- 6. 状态过滤 -->
|
<!-- 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'">
|
<if test="query.status != null and query.status != '' and query.status != 'all'">
|
||||||
<choose>
|
<choose>
|
||||||
<when test="'unbooked'.equals(query.status) or '未预约'.equals(query.status)">
|
<when test="'unbooked'.equals(query.status) or '未预约'.equals(query.status)">
|
||||||
@@ -318,7 +325,15 @@
|
|||||||
)
|
)
|
||||||
</when>
|
</when>
|
||||||
<when test="'booked'.equals(query.status) or '已预约'.equals(query.status)">
|
<when test="'booked'.equals(query.status) or '已预约'.equals(query.status)">
|
||||||
AND <include refid="slotStatusNormExpr" /> = 1
|
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 <include refid="orderStatusNormExpr" /> = 1
|
||||||
AND (
|
AND (
|
||||||
d.is_stopped IS NULL
|
d.is_stopped IS NULL
|
||||||
@@ -326,13 +341,7 @@
|
|||||||
)
|
)
|
||||||
</when>
|
</when>
|
||||||
<when test="'checked'.equals(query.status) or '已取号'.equals(query.status)">
|
<when test="'checked'.equals(query.status) or '已取号'.equals(query.status)">
|
||||||
AND (
|
AND <include refid="slotStatusNormExpr" /> = 1
|
||||||
<include refid="slotStatusNormExpr" /> = 3
|
|
||||||
OR (
|
|
||||||
<include refid="slotStatusNormExpr" /> = 1
|
|
||||||
AND <include refid="orderStatusNormExpr" /> = 2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
AND (
|
AND (
|
||||||
d.is_stopped IS NULL
|
d.is_stopped IS NULL
|
||||||
OR d.is_stopped = FALSE
|
OR d.is_stopped = FALSE
|
||||||
@@ -340,7 +349,7 @@
|
|||||||
</when>
|
</when>
|
||||||
<when test="'cancelled'.equals(query.status) or '已停诊'.equals(query.status) or '已取消'.equals(query.status)">
|
<when test="'cancelled'.equals(query.status) or '已停诊'.equals(query.status) or '已取消'.equals(query.status)">
|
||||||
AND (
|
AND (
|
||||||
<include refid="slotStatusNormExpr" /> = 2
|
<include refid="slotStatusNormExpr" /> = 4
|
||||||
OR d.is_stopped = TRUE
|
OR d.is_stopped = TRUE
|
||||||
)
|
)
|
||||||
</when>
|
</when>
|
||||||
|
|||||||
@@ -172,12 +172,12 @@ export const SlotStatus = {
|
|||||||
AVAILABLE: 0,
|
AVAILABLE: 0,
|
||||||
/** 已预约 */
|
/** 已预约 */
|
||||||
BOOKED: 1,
|
BOOKED: 1,
|
||||||
/** 已取消 / 已停诊 */
|
/** 已锁定 */
|
||||||
CANCELLED: 2,
|
LOCKED: 2,
|
||||||
/** 已签到 / 已取号 */
|
/** 已签到 / 已取号 */
|
||||||
CHECKED_IN: 3,
|
CHECKED_IN: 3,
|
||||||
/** 已锁定 */
|
/** 已取消 / 已停诊 */
|
||||||
LOCKED: 4,
|
CANCELLED: 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -185,10 +185,10 @@ export const SlotStatus = {
|
|||||||
*/
|
*/
|
||||||
export const SlotStatusDescriptions = {
|
export const SlotStatusDescriptions = {
|
||||||
0: '未预约',
|
0: '未预约',
|
||||||
1: '已预约',
|
1: '已取号',
|
||||||
2: '已停诊',
|
2: '已锁定',
|
||||||
3: '已取号',
|
3: '已取号',
|
||||||
4: '已锁定',
|
4: '已停诊',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
<select id="status-select" class="search-select" v-model="selectedStatus" @change="onSearch">
|
<select id="status-select" class="search-select" v-model="selectedStatus" @change="onSearch">
|
||||||
<option value="all">全部</option>
|
<option value="all">全部</option>
|
||||||
<option value="unbooked">未预约</option>
|
<option value="unbooked">未预约</option>
|
||||||
|
<option value="locked">已锁定</option>
|
||||||
<option value="booked">已预约</option>
|
<option value="booked">已预约</option>
|
||||||
<option value="checked">已取号</option>
|
<option value="checked">已取号</option>
|
||||||
<option value="cancelled">已停诊</option>
|
<option value="cancelled">已停诊</option>
|
||||||
@@ -253,6 +254,7 @@ import useUserStore from '@/store/modules/user';
|
|||||||
|
|
||||||
const STATUS_CLASS_MAP = {
|
const STATUS_CLASS_MAP = {
|
||||||
'未预约': 'status-unbooked',
|
'未预约': 'status-unbooked',
|
||||||
|
'已锁定': 'status-locked',
|
||||||
'已预约': 'status-booked',
|
'已预约': 'status-booked',
|
||||||
'已取号': 'status-checked',
|
'已取号': 'status-checked',
|
||||||
'已退号': 'status-returned',
|
'已退号': 'status-returned',
|
||||||
@@ -774,6 +776,7 @@ export default {
|
|||||||
// 🔧 BugFix#399: 确保已取号状态正确匹配
|
// 🔧 BugFix#399: 确保已取号状态正确匹配
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
unbooked: ['未预约'],
|
unbooked: ['未预约'],
|
||||||
|
locked: ['已锁定'],
|
||||||
booked: ['已预约'],
|
booked: ['已预约'],
|
||||||
checked: ['已取号', '已签到'],
|
checked: ['已取号', '已签到'],
|
||||||
cancelled: ['已停诊', '已取消'],
|
cancelled: ['已停诊', '已取消'],
|
||||||
|
|||||||
@@ -1685,7 +1685,7 @@ function loadCheckInPatientList() {
|
|||||||
const today = formatDateStr(new Date(), 'YYYY-MM-DD');
|
const today = formatDateStr(new Date(), 'YYYY-MM-DD');
|
||||||
listTicket({
|
listTicket({
|
||||||
date: today,
|
date: today,
|
||||||
status: 'booked',
|
status: 'locked',
|
||||||
name: checkInSearchKey.value, // 支持姓名等模糊查询,后端需适配
|
name: checkInSearchKey.value, // 支持姓名等模糊查询,后端需适配
|
||||||
page: checkInPage.value,
|
page: checkInPage.value,
|
||||||
limit: checkInLimit.value
|
limit: checkInLimit.value
|
||||||
|
|||||||
@@ -41,7 +41,9 @@
|
|||||||
<el-option label="全部" value="" />
|
<el-option label="全部" value="" />
|
||||||
<el-option label="待签发" value="0" />
|
<el-option label="待签发" value="0" />
|
||||||
<el-option label="已签发" value="1" />
|
<el-option label="已签发" value="1" />
|
||||||
<el-option label="已出报告" value="6" />
|
<el-option label="已采证" value="4" />
|
||||||
|
<el-option label="已送检" value="5" />
|
||||||
|
<el-option label="报告已出" value="6" />
|
||||||
<el-option label="已作废" value="7" />
|
<el-option label="已作废" value="7" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -91,7 +93,15 @@
|
|||||||
<el-table-column prop="prescriptionNo" label="申请单号" width="140" />
|
<el-table-column prop="prescriptionNo" label="申请单号" width="140" />
|
||||||
<el-table-column label="单据状态" width="100" align="center">
|
<el-table-column label="单据状态" width="100" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ parseBillStatus(scope.row.billStatus ?? scope.row.status) }}</span>
|
<el-tag
|
||||||
|
:type="getBillStatusTagType(scope.row)"
|
||||||
|
effect="plain"
|
||||||
|
round
|
||||||
|
:class="{ 'report-status-tag': isReportStatus(scope.row) }"
|
||||||
|
@click="handleStatusClick(scope.row)"
|
||||||
|
>
|
||||||
|
{{ parseBillStatus(getBillStatus(scope.row)) }}
|
||||||
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="申请类型" width="100" align="center">
|
<el-table-column label="申请类型" width="100" align="center">
|
||||||
@@ -107,16 +117,16 @@
|
|||||||
<el-table-column prop="requesterId_dictText" label="申请者" width="120" />
|
<el-table-column prop="requesterId_dictText" label="申请者" width="120" />
|
||||||
<el-table-column label="操作" align="center" fixed="right" width="220">
|
<el-table-column label="操作" align="center" fixed="right" width="220">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<!-- 待签发(status=0或null/undefined):可修改、删除 -->
|
<!-- 待签发:可修改、删除 -->
|
||||||
<template v-if="!scope.row.status || scope.row.status == 0">
|
<template v-if="isPendingStatus(scope.row)">
|
||||||
<el-button link type="primary" @click="handleEdit(scope.row)">修改</el-button>
|
<el-button link type="primary" @click="handleEdit(scope.row)">修改</el-button>
|
||||||
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
<!-- 已签发(status=1):可撤回 -->
|
<!-- 已签发:可撤回 -->
|
||||||
<template v-else-if="scope.row.status == 1">
|
<template v-else-if="isIssuedStatus(scope.row)">
|
||||||
<el-button link type="warning" @click="handleWithdraw(scope.row)">撤回</el-button>
|
<el-button link type="warning" @click="handleWithdraw(scope.row)">撤回</el-button>
|
||||||
</template>
|
</template>
|
||||||
<!-- 已校对(2)、待接收(3)、已收样(4)、已出报告(6)、已作废(7):仅查看详情 -->
|
<!-- 已采证、已送检、报告已出、已作废:仅查看详情 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
|
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -212,10 +222,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {computed, getCurrentInstance, ref, watch} from 'vue';
|
import {computed, getCurrentInstance, nextTick, ref, watch} from 'vue';
|
||||||
import {Refresh, Search} from '@element-plus/icons-vue';
|
import {Refresh, Search} from '@element-plus/icons-vue';
|
||||||
import {patientInfo} from '../../store/patient.js';
|
import {patientInfo} from '../../store/patient.js';
|
||||||
import {getInspection, deleteRequestForm, withdrawRequestForm} from './api';
|
import {getInspection, deleteRequestForm, withdrawRequestForm, getProofResult} from './api';
|
||||||
import {getDepartmentList} from '@/api/public.js';
|
import {getDepartmentList} from '@/api/public.js';
|
||||||
import LaboratoryTests from '../order/applicationForm/laboratoryTests.vue';
|
import LaboratoryTests from '../order/applicationForm/laboratoryTests.vue';
|
||||||
import {saveInspection} from '../order/applicationForm/api.js';
|
import {saveInspection} from '../order/applicationForm/api.js';
|
||||||
@@ -270,7 +280,7 @@ const fetchData = async () => {
|
|||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
const raw = res.data?.records || res.data;
|
const raw = res.data?.records || res.data;
|
||||||
const list = Array.isArray(raw) ? raw : [raw];
|
const list = Array.isArray(raw) ? raw : [raw];
|
||||||
tableData.value = list.filter(Boolean);
|
tableData.value = list.filter(Boolean).sort(sortByCreateTimeDesc);
|
||||||
} else {
|
} else {
|
||||||
tableData.value = [];
|
tableData.value = [];
|
||||||
}
|
}
|
||||||
@@ -329,19 +339,95 @@ const labelMap = {
|
|||||||
* @param {string|number} status - 状态码
|
* @param {string|number} status - 状态码
|
||||||
* @returns {string} 状态文本
|
* @returns {string} 状态文本
|
||||||
*/
|
*/
|
||||||
|
const getBillStatus = (row) => {
|
||||||
|
return row?.billStatus ?? row?.status ?? row?.statusEnum ?? row?.applyStatus;
|
||||||
|
};
|
||||||
|
|
||||||
const parseBillStatus = (status) => {
|
const parseBillStatus = (status) => {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
'0': '待签发',
|
'0': '待签发',
|
||||||
'1': '已签发',
|
'1': '已签发',
|
||||||
'2': '已校对',
|
'2': '已采证',
|
||||||
'3': '待接收',
|
'3': '已送检',
|
||||||
'4': '已收样',
|
'4': '已采证',
|
||||||
'6': '已出报告',
|
'5': '已送检',
|
||||||
|
'6': '报告已出',
|
||||||
|
'8': '报告已出',
|
||||||
'7': '已作废',
|
'7': '已作废',
|
||||||
};
|
};
|
||||||
return statusMap[String(status)] || '-';
|
return statusMap[String(status)] || '-';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getBillStatusTagType = (row) => {
|
||||||
|
const typeMap = {
|
||||||
|
'0': 'info',
|
||||||
|
'1': 'primary',
|
||||||
|
'2': 'primary',
|
||||||
|
'3': 'warning',
|
||||||
|
'4': 'primary',
|
||||||
|
'5': 'warning',
|
||||||
|
'6': 'success',
|
||||||
|
'7': 'danger',
|
||||||
|
'8': 'success',
|
||||||
|
};
|
||||||
|
return typeMap[String(getBillStatus(row))] || 'info';
|
||||||
|
};
|
||||||
|
|
||||||
|
const isPendingStatus = (row) => {
|
||||||
|
const status = getBillStatus(row);
|
||||||
|
return status === undefined || status === null || status === '' || String(status) === '0';
|
||||||
|
};
|
||||||
|
|
||||||
|
const isIssuedStatus = (row) => String(getBillStatus(row)) === '1';
|
||||||
|
|
||||||
|
const isReportStatus = (row) => ['6', '8'].includes(String(getBillStatus(row)));
|
||||||
|
|
||||||
|
const sortByCreateTimeDesc = (a, b) => {
|
||||||
|
const aTime = a?.createTime ? new Date(a.createTime).getTime() : 0;
|
||||||
|
const bTime = b?.createTime ? new Date(b.createTime).getTime() : 0;
|
||||||
|
return bTime - aTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStatusClick = (row) => {
|
||||||
|
if (isReportStatus(row)) {
|
||||||
|
handleViewReport(row);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pickReportUrl = (data, row) => {
|
||||||
|
if (!data) return '';
|
||||||
|
if (typeof data === 'string') return data;
|
||||||
|
|
||||||
|
const raw = data.records || data;
|
||||||
|
const list = Array.isArray(raw) ? raw : [raw];
|
||||||
|
const matched =
|
||||||
|
list.find((item) => {
|
||||||
|
const reportNo = item.busNo || item.reportNo || item.applyNo || item.prescriptionNo;
|
||||||
|
return reportNo && row.prescriptionNo && String(reportNo) === String(row.prescriptionNo);
|
||||||
|
}) || list[0];
|
||||||
|
|
||||||
|
return matched?.requestUrl || matched?.pdfUrl || matched?.reportUrl || matched?.url || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleViewReport = async (row) => {
|
||||||
|
try {
|
||||||
|
const res = await getProofResult({
|
||||||
|
encounterId: row.encounterId || patientInfo.value?.encounterId,
|
||||||
|
prescriptionNo: row.prescriptionNo,
|
||||||
|
});
|
||||||
|
if (res?.code === 200) {
|
||||||
|
const url = pickReportUrl(res.data, row);
|
||||||
|
if (url) {
|
||||||
|
window.open(url, '_blank');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
proxy.$modal?.msgWarning?.('暂未获取到检验报告链接');
|
||||||
|
} catch (e) {
|
||||||
|
proxy.$modal?.msgError?.(e.message || '获取检验报告失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析申请类型(优先级代码)
|
* 解析申请类型(优先级代码)
|
||||||
* @param {string} descJson - JSON字符串
|
* @param {string} descJson - JSON字符串
|
||||||
@@ -462,12 +548,12 @@ const handleViewDetail = async (row) => {
|
|||||||
* 修改检验申请单(待签发状态)
|
* 修改检验申请单(待签发状态)
|
||||||
*/
|
*/
|
||||||
const handleEdit = async (row) => {
|
const handleEdit = async (row) => {
|
||||||
// 确保科室数据已加载
|
|
||||||
if (!orgOptions.value || orgOptions.value.length === 0) {
|
|
||||||
await getLocationInfo();
|
|
||||||
}
|
|
||||||
editRowData.value = row;
|
editRowData.value = row;
|
||||||
editDialogVisible.value = true;
|
editDialogVisible.value = true;
|
||||||
|
await nextTick();
|
||||||
|
editFormRef.value?.getList?.();
|
||||||
|
editFormRef.value?.getLocationInfo?.();
|
||||||
|
editFormRef.value?.getDiagnosisList?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -646,6 +732,10 @@ defineExpose({
|
|||||||
animation: rotating 2s linear infinite;
|
animation: rotating 2s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.report-status-tag {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes rotating {
|
@keyframes rotating {
|
||||||
0% {
|
0% {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
|
|||||||
@@ -134,10 +134,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup name="LaboratoryTests">
|
<script setup name="LaboratoryTests">
|
||||||
import {getCurrentInstance, onMounted, reactive, ref, watch, computed} from 'vue';
|
import {getCurrentInstance, nextTick, onMounted, reactive, ref, watch, computed} from 'vue';
|
||||||
import {patientInfo} from '../../../store/patient.js';
|
import {patientInfo} from '../../../store/patient.js';
|
||||||
import {getApplicationList, saveInspection} from './api';
|
import {getApplicationList, saveInspection} from './api';
|
||||||
import {getOrgList} from '@/views/doctorstation/components/api.js';
|
import {getDepartmentList} from '@/api/public.js';
|
||||||
import {getEncounterDiagnosis} from '../../api.js';
|
import {getEncounterDiagnosis} from '../../api.js';
|
||||||
import {ElMessage} from 'element-plus';
|
import {ElMessage} from 'element-plus';
|
||||||
|
|
||||||
@@ -168,6 +168,7 @@ const loading = ref(false);
|
|||||||
const orgOptions = ref([]);
|
const orgOptions = ref([]);
|
||||||
const searchKey = ref('');
|
const searchKey = ref('');
|
||||||
const totalCount = ref(0);
|
const totalCount = ref(0);
|
||||||
|
const skipDeptAutoFill = ref(false);
|
||||||
|
|
||||||
// 将已加载的全部数据转为 transfer 组件所需的格式
|
// 将已加载的全部数据转为 transfer 组件所需的格式
|
||||||
const buildTransferData = (records) => {
|
const buildTransferData = (records) => {
|
||||||
@@ -263,7 +264,31 @@ const form = reactive({
|
|||||||
otherDiagnosisList: [], //其他断目录
|
otherDiagnosisList: [], //其他断目录
|
||||||
});
|
});
|
||||||
const rules = reactive({});
|
const rules = reactive({});
|
||||||
|
|
||||||
|
const normalizeOrgTreeIds = (nodes) => {
|
||||||
|
if (!Array.isArray(nodes)) return [];
|
||||||
|
return nodes.map((node) => ({
|
||||||
|
...node,
|
||||||
|
id: node.id != null ? String(node.id) : node.id,
|
||||||
|
children: node.children?.length ? normalizeOrgTreeIds(node.children) : undefined,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveTargetDepartmentId = (rawId) => {
|
||||||
|
if (rawId == null || rawId === '') return '';
|
||||||
|
const node = findTreeItem(orgOptions.value, rawId);
|
||||||
|
return node ? String(node.id) : String(rawId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyTargetDepartmentEcho = () => {
|
||||||
|
if (form.targetDepartment) {
|
||||||
|
form.targetDepartment = resolveTargetDepartmentId(form.targetDepartment);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
getLocationInfo();
|
||||||
|
getDiagnosisList();
|
||||||
getList();
|
getList();
|
||||||
});
|
});
|
||||||
/**
|
/**
|
||||||
@@ -292,8 +317,9 @@ const projectWithDepartment = (selectProjectIds, type) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 保存用户手动选择的发往科室(提交时需要保留)
|
// 保存用户手动选择/回显的发往科室(提交、编辑回显时需要保留)
|
||||||
const manualDept = type === 2 ? form.targetDepartment : '';
|
const manualDept =
|
||||||
|
type === 2 || (isEditMode.value && form.targetDepartment) ? form.targetDepartment : '';
|
||||||
// 清空科室
|
// 清空科室
|
||||||
form.targetDepartment = '';
|
form.targetDepartment = '';
|
||||||
if (arr.length > 0) {
|
if (arr.length > 0) {
|
||||||
@@ -313,8 +339,8 @@ const projectWithDepartment = (selectProjectIds, type) => {
|
|||||||
const findItem = findTreeItem(orgOptions.value, obj.orgId);
|
const findItem = findTreeItem(orgOptions.value, obj.orgId);
|
||||||
if (!findItem) {
|
if (!findItem) {
|
||||||
// type=2(提交)时,若用户已手动选择发往科室,则允许提交
|
// type=2(提交)时,若用户已手动选择发往科室,则允许提交
|
||||||
if (type === 2 && manualDept) {
|
if ((type === 2 || isEditMode.value) && manualDept) {
|
||||||
form.targetDepartment = manualDept;
|
form.targetDepartment = resolveTargetDepartmentId(manualDept);
|
||||||
isRelease = true;
|
isRelease = true;
|
||||||
} else if (type === 2 && !manualDept) {
|
} else if (type === 2 && !manualDept) {
|
||||||
// 提交时用户未手动选择科室,才提示错误
|
// 提交时用户未手动选择科室,才提示错误
|
||||||
@@ -330,10 +356,10 @@ const projectWithDepartment = (selectProjectIds, type) => {
|
|||||||
}
|
}
|
||||||
if (findItem && isRelease) {
|
if (findItem && isRelease) {
|
||||||
// 提交时若用户已选「发往科室」,不得用项目默认执行科室覆盖
|
// 提交时若用户已选「发往科室」,不得用项目默认执行科室覆盖
|
||||||
if (type === 2 && manualDept) {
|
if ((type === 2 || isEditMode.value) && manualDept) {
|
||||||
form.targetDepartment = manualDept;
|
form.targetDepartment = resolveTargetDepartmentId(manualDept);
|
||||||
} else {
|
} else {
|
||||||
form.targetDepartment = findItem.id;
|
form.targetDepartment = String(findItem.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,6 +369,7 @@ const projectWithDepartment = (selectProjectIds, type) => {
|
|||||||
watch(
|
watch(
|
||||||
() => transferValue.value,
|
() => transferValue.value,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
|
if (skipDeptAutoFill.value) return;
|
||||||
if (isInitializing.value) return;
|
if (isInitializing.value) return;
|
||||||
projectWithDepartment(newValue, 1);
|
projectWithDepartment(newValue, 1);
|
||||||
}
|
}
|
||||||
@@ -382,7 +409,11 @@ const applyEditTransferSelection = () => {
|
|||||||
const uniq = [...new Set(selectedIds)]
|
const uniq = [...new Set(selectedIds)]
|
||||||
// 设置初始化标志,防止 transferValue 变化触发 projectWithDepartment 覆盖 descJson 中的科室值
|
// 设置初始化标志,防止 transferValue 变化触发 projectWithDepartment 覆盖 descJson 中的科室值
|
||||||
isInitializing.value = true
|
isInitializing.value = true
|
||||||
|
skipDeptAutoFill.value = true
|
||||||
transferValue.value = uniq
|
transferValue.value = uniq
|
||||||
|
nextTick(() => {
|
||||||
|
skipDeptAutoFill.value = false
|
||||||
|
})
|
||||||
isInitializing.value = false
|
isInitializing.value = false
|
||||||
if (newData.requestFormDetailList.length && uniq.length === 0) {
|
if (newData.requestFormDetailList.length && uniq.length === 0) {
|
||||||
console.warn(
|
console.warn(
|
||||||
@@ -406,6 +437,7 @@ watch(
|
|||||||
form[key] = obj[key]
|
form[key] = obj[key]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
applyTargetDepartmentEcho()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('解析 descJson 失败:', e)
|
console.error('解析 descJson 失败:', e)
|
||||||
}
|
}
|
||||||
@@ -416,7 +448,14 @@ watch(
|
|||||||
{ immediate: true, deep: true }
|
{ immediate: true, deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
// 编辑模式下,applicationListAll 加载完成后重新回显已选项目
|
watch(
|
||||||
|
() => orgOptions.value,
|
||||||
|
() => {
|
||||||
|
applyTargetDepartmentEcho()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 编辑模式下,项目字典加载完成后重新回显已选项目
|
||||||
watch(
|
watch(
|
||||||
() => applicationListAll.value,
|
() => applicationListAll.value,
|
||||||
() => {
|
() => {
|
||||||
@@ -436,6 +475,7 @@ watch(
|
|||||||
isInitializing.value = true;
|
isInitializing.value = true;
|
||||||
transferValue.value = selectedIds;
|
transferValue.value = selectedIds;
|
||||||
isInitializing.value = false;
|
isInitializing.value = false;
|
||||||
|
applyEditTransferSelection();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -488,9 +528,9 @@ const submit = () => {
|
|||||||
};
|
};
|
||||||
/** 查询科室 */
|
/** 查询科室 */
|
||||||
const getLocationInfo = () => {
|
const getLocationInfo = () => {
|
||||||
getOrgList().then((res) => {
|
return getDepartmentList().then((res) => {
|
||||||
orgOptions.value = res.data.records;
|
orgOptions.value = normalizeOrgTreeIds(res.data || []);
|
||||||
console.log('科室========>', JSON.stringify(orgOptions.value));
|
applyTargetDepartmentEcho();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 获取诊断目录
|
// 获取诊断目录
|
||||||
|
|||||||
@@ -804,7 +804,7 @@ function checkUnit(item, row) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 行双击打开编辑块,"待保存"和"待签发"均可编辑
|
// 行双击打开编辑块:待保存、待签发医嘱均可编辑;已签发/已完成/停止不允许编辑
|
||||||
function clickRowDb(row, column, event) {
|
function clickRowDb(row, column, event) {
|
||||||
// 检查点击的是否是复选框
|
// 检查点击的是否是复选框
|
||||||
if (event && event.target.closest('.el-checkbox')) {
|
if (event && event.target.closest('.el-checkbox')) {
|
||||||
@@ -815,14 +815,18 @@ function clickRowDb(row, column, event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
row.showPopover = false;
|
row.showPopover = false;
|
||||||
// statusEnum == 1 包含"待保存(无requestId)"和"待签发(有requestId)",均允许编辑
|
|
||||||
if (row.statusEnum == 1) {
|
if (row.statusEnum == 1) {
|
||||||
// 确保治疗类型为字符串,方便与单选框 label 对齐,默认为长期医嘱('1')
|
// 确保治疗类型为字符串,方便与单选框 label 对齐,默认为长期医嘱('1')
|
||||||
row.therapyEnum = String(row.therapyEnum ?? '1');
|
row.therapyEnum = String(row.therapyEnum ?? '1');
|
||||||
row.isEdit = true;
|
row.isEdit = true;
|
||||||
const index = prescriptionList.value.findIndex((item) => item.uniqueKey === row.uniqueKey);
|
const index = prescriptionList.value.findIndex((item) => item.uniqueKey === row.uniqueKey);
|
||||||
prescriptionList.value[index] = row;
|
rowIndex.value = index;
|
||||||
|
if (index !== -1) {
|
||||||
|
prescriptionList.value[index] = row;
|
||||||
|
}
|
||||||
expandOrder.value = [row.uniqueKey];
|
expandOrder.value = [row.uniqueKey];
|
||||||
|
} else {
|
||||||
|
proxy.$modal.msgWarning('仅待保存或待签发医嘱允许编辑');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1393,7 +1397,9 @@ function handleSaveSign(row, index) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (prescriptionList.value[0].adviceName) {
|
// 仅通过【新增】按钮创建的医嘱保存后才自动添加下一行空医嘱
|
||||||
|
// 双击编辑已有"待保存"医嘱保存时,不应自动添加空行
|
||||||
|
if (isAdding.value && prescriptionList.value[0].adviceName) {
|
||||||
handleAddPrescription();
|
handleAddPrescription();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -348,7 +348,8 @@ const adviceTypeList = computed(() => {
|
|||||||
return val === 3 || val === 4;
|
return val === 3 || val === 4;
|
||||||
}).map(item => ({
|
}).map(item => ({
|
||||||
label: item.label,
|
label: item.label,
|
||||||
value: parseInt(item.value)
|
// drord_doctor_type 中耗材是 4,但 /advice-base-info 后端耗材类型是 2
|
||||||
|
value: parseInt(item.value) === 4 ? 2 : parseInt(item.value)
|
||||||
}));
|
}));
|
||||||
return [...filtered, { label: '全部', value: '' }];
|
return [...filtered, { label: '全部', value: '' }];
|
||||||
}
|
}
|
||||||
@@ -483,8 +484,9 @@ watch(
|
|||||||
(visible) => {
|
(visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
executeTime.value = formatDateStr(new Date(), 'YYYY-MM-DD HH:mm:ss');
|
executeTime.value = formatDateStr(new Date(), 'YYYY-MM-DD HH:mm:ss');
|
||||||
// 弹窗打开时重新加载科室和位置选项,确保数据最新
|
// 弹窗打开时按当前患者科室重新加载,避免复用上一次患者/登录科室的结果
|
||||||
loadDepartmentOptions();
|
loadDepartmentOptions();
|
||||||
|
getAdviceBaseInfos();
|
||||||
getDiseaseInitLoc(16);
|
getDiseaseInitLoc(16);
|
||||||
} else {
|
} else {
|
||||||
resetData();
|
resetData();
|
||||||
@@ -565,6 +567,8 @@ function getAdviceBaseInfos() {
|
|||||||
queryParams.value.adviceTypes = [1, 2, 3];
|
queryParams.value.adviceTypes = [1, 2, 3];
|
||||||
}
|
}
|
||||||
queryParams.value.organizationId = orgId.value;
|
queryParams.value.organizationId = orgId.value;
|
||||||
|
queryParams.value.adviceTypes = normalizeAdviceTypesForQuery(adviceType.value);
|
||||||
|
queryParams.value.organizationId = props.patientInfo.organizationId || orgId.value;
|
||||||
queryParams.value.pricingFlag = 1; // 划价标记
|
queryParams.value.pricingFlag = 1; // 划价标记
|
||||||
getAdviceBaseInfo(queryParams.value)
|
getAdviceBaseInfo(queryParams.value)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@@ -620,6 +624,12 @@ function getItemType_Text(type) {
|
|||||||
const map = { 2: '耗材', 3: '诊疗' };
|
const map = { 2: '耗材', 3: '诊疗' };
|
||||||
return map[type] || '其他';
|
return map[type] || '其他';
|
||||||
}
|
}
|
||||||
|
function normalizeAdviceTypesForQuery(type) {
|
||||||
|
if (type === '' || type === undefined || type === null) {
|
||||||
|
return '2,3';
|
||||||
|
}
|
||||||
|
return Number(type) === 4 ? 2 : type;
|
||||||
|
}
|
||||||
function getUnitCodeOptions(row) {
|
function getUnitCodeOptions(row) {
|
||||||
const unitCodes = [];
|
const unitCodes = [];
|
||||||
// 大单位:优先用 code,code 缺失时用字典文本兜底
|
// 大单位:优先用 code,code 缺失时用字典文本兜底
|
||||||
|
|||||||
@@ -262,7 +262,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {computed, nextTick, onMounted, ref, watch} from 'vue';
|
import {nextTick, onMounted, ref} from 'vue';
|
||||||
import {ElMessage, ElMessageBox} from 'element-plus';
|
import {ElMessage, ElMessageBox} from 'element-plus';
|
||||||
// Element Plus 图标导入
|
// Element Plus 图标导入
|
||||||
import {User} from '@element-plus/icons-vue';
|
import {User} from '@element-plus/icons-vue';
|
||||||
@@ -366,9 +366,9 @@ const rawPrescriptionList = ref([]); // 原始未分组数据
|
|||||||
const groupedPrescriptionList = ref([]); // 按encounterId分组后的数据
|
const groupedPrescriptionList = ref([]); // 按encounterId分组后的数据
|
||||||
const activeCollapseNames = ref([]); // Collapse激活状态
|
const activeCollapseNames = ref([]); // Collapse激活状态
|
||||||
const selectedRows = ref({}); // 选中的行数据
|
const selectedRows = ref({}); // 选中的行数据
|
||||||
const totalItemsCount = ref(0); // 总医嘱项数
|
|
||||||
const totalAmount = ref(0); // 总金额(保留4位小数)
|
|
||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
|
/** Tab 切换同步日期时跳过 date-picker change,避免与 v-model 循环触发 */
|
||||||
|
const syncingDateFromTab = ref(false);
|
||||||
const selectedFeeItems = ref([]);
|
const selectedFeeItems = ref([]);
|
||||||
const currentPatientInfo = ref(null);
|
const currentPatientInfo = ref(null);
|
||||||
const queryParams = ref({
|
const queryParams = ref({
|
||||||
@@ -381,24 +381,6 @@ const userStore = useUserStore();
|
|||||||
const userId = ref(safeGet(userStore, 'id', ''));
|
const userId = ref(safeGet(userStore, 'id', ''));
|
||||||
const orgId = ref(safeGet(userStore, 'orgId', ''));
|
const orgId = ref(safeGet(userStore, 'orgId', ''));
|
||||||
|
|
||||||
// ========== 计算属性 ==========
|
|
||||||
// 计算总统计信息(总项数、总金额)
|
|
||||||
const calculateTotalStats = computed(() => {
|
|
||||||
let itemsCount = 0;
|
|
||||||
let amount = 0;
|
|
||||||
|
|
||||||
safeArray(groupedPrescriptionList.value).forEach((patientGroup) => {
|
|
||||||
safeArray(patientGroup).forEach((item) => {
|
|
||||||
itemsCount++;
|
|
||||||
// 累加单价,保留4位小数精度
|
|
||||||
amount = Math.round((amount + Number(safeGet(item, 'unitPrice', 0))) * 10000) / 10000;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
totalItemsCount.value = itemsCount;
|
|
||||||
totalAmount.value = amount;
|
|
||||||
});
|
|
||||||
|
|
||||||
// ========== 方法 ==========
|
// ========== 方法 ==========
|
||||||
/**
|
/**
|
||||||
* 计算单个患者的总金额(保留4位小数)
|
* 计算单个患者的总金额(保留4位小数)
|
||||||
@@ -447,16 +429,19 @@ const handleTableSelectionChange = (index, val) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日期Tab切换
|
* 按 Tab 同步日期范围(避免 date-picker @change 与 Tab v-model 互相覆盖)
|
||||||
* @param {Object} tab - 标签页
|
* @param {string} rangeType - today | yesterday | custom
|
||||||
*/
|
*/
|
||||||
const handleDateTabClick = (tab) => {
|
const applyDateRangeByTab = (rangeType) => {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const yesterday = new Date(today);
|
const yesterday = new Date(today);
|
||||||
yesterday.setDate(today.getDate() - 1);
|
yesterday.setDate(today.getDate() - 1);
|
||||||
const format = (date) => formatDateStr(date, 'YYYY-MM-DD');
|
const format = (date) => formatDateStr(date, 'YYYY-MM-DD');
|
||||||
|
|
||||||
switch (safeGet(tab, 'paneName')) {
|
syncingDateFromTab.value = true;
|
||||||
|
dateRange.value = rangeType;
|
||||||
|
|
||||||
|
switch (rangeType) {
|
||||||
case 'today':
|
case 'today':
|
||||||
dateRangeValue.value = [format(today), format(today)];
|
dateRangeValue.value = [format(today), format(today)];
|
||||||
break;
|
break;
|
||||||
@@ -464,27 +449,54 @@ const handleDateTabClick = (tab) => {
|
|||||||
dateRangeValue.value = [format(yesterday), format(yesterday)];
|
dateRangeValue.value = [format(yesterday), format(yesterday)];
|
||||||
break;
|
break;
|
||||||
case 'custom':
|
case 'custom':
|
||||||
if (!dateRangeValue.value.length) {
|
if (safeArray(dateRangeValue.value).length < 2) {
|
||||||
dateRangeValue.value = [format(today), format(today)];
|
dateRangeValue.value = [format(today), format(today)];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
syncingDateFromTab.value = false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日期选择器变化
|
* 日期Tab切换
|
||||||
|
* @param {Object} tab - 标签页
|
||||||
|
*/
|
||||||
|
const handleDateTabClick = (tab) => {
|
||||||
|
const rangeType = tab?.paneName ?? tab?.props?.name;
|
||||||
|
if (!rangeType) return;
|
||||||
|
applyDateRangeByTab(rangeType);
|
||||||
|
handleQuery();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日期选择器变化(仅用户手动改日期时切到「自定义」)
|
||||||
* @param {Array} val - 选中日期
|
* @param {Array} val - 选中日期
|
||||||
*/
|
*/
|
||||||
const handleDatePickerChange = (val) => {
|
const handleDatePickerChange = (val) => {
|
||||||
|
if (syncingDateFromTab.value) return;
|
||||||
|
|
||||||
const dateVal = safeArray(val);
|
const dateVal = safeArray(val);
|
||||||
if (dateVal.length === 2) {
|
if (dateVal.length !== 2) return;
|
||||||
|
|
||||||
|
const start = new Date(dateVal[0]);
|
||||||
|
const end = new Date(dateVal[1]);
|
||||||
|
if (start > end) {
|
||||||
|
ElMessage.warning('开始日期不能晚于结束日期');
|
||||||
|
syncingDateFromTab.value = true;
|
||||||
|
dateRangeValue.value = [dateVal[1], dateVal[0]];
|
||||||
|
nextTick(() => {
|
||||||
|
syncingDateFromTab.value = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dateRange.value !== 'custom') {
|
||||||
dateRange.value = 'custom';
|
dateRange.value = 'custom';
|
||||||
const start = new Date(dateVal[0]);
|
|
||||||
const end = new Date(dateVal[1]);
|
|
||||||
if (start > end) {
|
|
||||||
ElMessage.warning('开始日期不能晚于结束日期');
|
|
||||||
dateRangeValue.value = [dateVal[1], dateVal[0]];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -714,24 +726,7 @@ const handleSingleDelete = (row) => {
|
|||||||
};
|
};
|
||||||
// ========== 初始化 ==========
|
// ========== 初始化 ==========
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 设置默认日期
|
applyDateRangeByTab('today');
|
||||||
const today = new Date();
|
|
||||||
const defaultDate = formatDateStr(today, 'YYYY-MM-DD');
|
|
||||||
dateRangeValue.value = [defaultDate, defaultDate];
|
|
||||||
|
|
||||||
// 监听日期变化自动查询
|
|
||||||
watch(
|
|
||||||
[dateRange, dateRangeValue],
|
|
||||||
([newRange, newVal], [oldRange, oldVal]) => {
|
|
||||||
if (oldRange !== undefined && safeArray(newVal).length === 2) {
|
|
||||||
handleQuery();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// 初始化统计信息
|
|
||||||
calculateTotalStats.value;
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -150,7 +150,7 @@
|
|||||||
:disabled="allItemsSubmitted"
|
:disabled="allItemsSubmitted"
|
||||||
@click="handleSignAndSubmit"
|
@click="handleSignAndSubmit"
|
||||||
>
|
>
|
||||||
{{ allItemsSubmitted ? '已签发' : (isSigned ? '提交医嘱' : '一键签名并生成医嘱') }}
|
{{ allItemsSubmitted ? '已签发' : '一键签名并生成医嘱' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user