55 Commits

Author SHA1 Message Date
HuangXinQuan
0c5353cf8b 300,301,302预约挂号展示问题 2026-04-03 16:47:03 +08:00
Ranyunqiao
8a84b40ee5 333 门诊医生站开立耗材医嘱时,类型误转为“中成药”且保存报错 2026-04-03 16:42:10 +08:00
f6b39a4815 fix: 更新门诊定价服务以仅返回划价标记为“是”的项目,并修正日志路径和VitalSigns表名
- 修改 OutpatientPricingAppServiceImpl.java,确保仅返回划价标记为“是”的项目
- 修正 VitalSigns.java 中的表名为 "doc_vital_signs"
2026-04-03 16:35:21 +08:00
HuangXinQuan
1b3d4e3dc0 77 门诊挂号-》预约签到 2026-04-03 14:42:13 +08:00
his-dev
cb46461ede fix(#303): 将取消预约限制从取消操作移至预约挂号操作
问题:取消预约时检查次数限制,导致用户无法取消预约
修复:将取消次数限制检查移到预约挂号时进行

变更:
- bookTicket(): 添加取消次数限制检查,达到上限禁止预约
- cancelTicket(): 移除取消限制检查,允许正常取消

提示信息:"由于您在月度内累计取消预约已达X次,触发系统限制,暂时无法在线预约,请联系分诊台或咨询客服。"
2026-04-03 14:08:23 +08:00
3b0a359412 fix: 修复日期格式化函数,支持不带前导零的 M/D 格式
- 修改 formatDateStr 函数,添加对 M/ 和 /D 格式的支持
- 确保生成的日期格式与后端期望的 yyyy/M/d HH:mm:ss 格式匹配
2026-04-03 11:02:58 +08:00
6fa26e895d Merge remote-tracking branch 'origin/develop' into develop 2026-04-03 10:59:04 +08:00
8ab8691c17 fix: 修复禅道Bug #330 门诊医生站诊断保存失败问题
- 修改前端日期格式,从ISO格式改为 yyyy/M/d HH:mm:ss 格式
- 添加后端参数校验,防止NPE异常
- 优化前端错误提示,显示后端返回的具体错误信息
2026-04-03 10:58:23 +08:00
Ranyunqiao
35b8a7d10a 320 手术管理-》门诊手术安排:新增手术安排界面的就诊卡号取值错误 2026-04-03 10:45:19 +08:00
22de02f132 fix: 恢复 IChargeBillServiceImpl.java 中被意外删除的方法
- 恢复 getTotalCcu、getDaySumByTime、getTotalOut 等方法

- 修复编译错误
2026-04-03 09:37:06 +08:00
11244aa48f fix: 修复收费失败错误 'element cannot be mapped to a null key' - 根本原因
- 修复 PaymentRecStaticServiceImpl.java 第 49、52、55、58 行

- 添加对 ChargeItemDefInfo::getTypeCode 和 ChargeItemDefInfo::getYbType 的 null 过滤

- 修复 IChargeBillServiceImpl.java 第 657 行 Invoice::getReconciliationId
2026-04-03 08:32:04 +08:00
0a5f26e9c0 fix: 修复收费失败错误 'element cannot be mapped to a null key' - 补充修复
- 修复 PaymentRecServiceImpl.java 第 2472 行 groupingBy(Account::getId)

- 修复 PaymentRecServiceImpl.java 第 264 行 groupingBy(ChargeItem::getAccountId)

- 修复 IChargeBillServiceImpl.java 多处 groupingBy 可能遇到的 null key 问题
2026-04-02 18:44:06 +08:00
4a8e9b5a22 Merge branch 'develop' of https://gitea.gentronhealth.com/wangyizhe/his into develop 2026-04-02 18:22:35 +08:00
bfb2491842 fix: 修复收费失败错误 'element cannot be mapped to a null key'
- 在 PaymentRecServiceImpl.java 中添加过滤,排除 contractNo 为 null 的数据

- 在 IChargeBillServiceImpl.java 中添加过滤,排除 contractNo 为 null 的数据

- 防止 Java Stream groupingBy 操作时出现 null key 异常
2026-04-02 18:22:18 +08:00
wangjian963
b747f80507 feat(doctorstation): 检验申请单列表添加申请ID字段
- DTO添加applicationId(自增主键)字段
- Mapper返回类型从实体类改为DTO
- 前端表格显示申请ID替代行号
- 调整UI布局和分页器样式
2026-04-02 17:59:21 +08:00
ced931a280 Merge remote-tracking branch 'origin/develop' into develop 2026-04-02 17:54:31 +08:00
b497eb853c fix(surgery): 解决手术申请中的数据绑定和字段映射问题
- 修复了手术申请组件中 userStore 初始化问题,确保 applyDoctorName 和 applyDeptName 正确赋值
- 添加了 surgeryApplication 组件的 saved 事件发射,用于通知父组件刷新医嘱列表
- 修复了手术项目选择变更时 surgeryName 的正确设置和空值处理
- 添加了手术名称和编码的验证逻辑,防止提交时出现空值错误
- 修复了手术排班页面中就诊卡号字段的属性映射(visitId 改为 patientCardNo)
- 在后端 DTO 中添加了 patientCardNo 字段支持
- 修复了数据库查询中就诊卡号的关联查询逻辑,通过患者标识表获取正确的就诊卡号
- 优化了手术医嘱的 contentJson 设置,确保手术名称和编码正确存储
2026-04-02 17:54:07 +08:00
7a2342ea2e 311 检验项目设置-》检验项目:【新增】一条检验项目系统自动在《诊疗目录》增加一条检验收费项目
312检验项目设置-套餐设置:折扣%字段换算公式错误
319 住院管理》-住院医生站》-住院医生站保存患者诊断时报错
2026-04-02 17:25:28 +08:00
Ranyunqiao
09fdfa294a 重新发布 2026-04-02 15:31:56 +08:00
Ranyunqiao
4ef9aa07d2 91 分诊排队管理-》门诊医生站:【完诊】患者队列状态的变化 2026-04-02 15:23:42 +08:00
08085403b3 Merge remote-tracking branch 'origin/develop' into develop 2026-04-02 08:46:09 +08:00
2d7dcb4aeb fix(inpatient): 解决手术申请单数据同步和命名问题
- 在应用表单底部按钮中添加延迟刷新机制,确保后端数据提交完成后再触发刷新事件
- 在手术组件中添加诊疗定义名称字段,完善手术项目信息传递
- 优化手术医嘱生成功能,添加详细的调试日志以便追踪问题
- 修复手术项目名称获取逻辑,优先使用activityList中的手术项目名称
- 完善手术收费项目生成流程,添加异常处理和日志记录
- 在控制器中添加手术申请单保存的日志输出,便于问题排查
2026-04-02 08:15:11 +08:00
ad29502488 Fix: 帮助文档打包失败v2 2026-04-01 18:56:51 +08:00
5b0acede89 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	openhis-ui-vue3/src/views/clinicmanagement/bargain/component/prescriptionlist.vue
2026-04-01 18:27:31 +08:00
ac1cd3afc8 fix(prescription): 解决处方列表中手术类型和其他医嘱类型的问题
- 更新 lodash.template 修复脚本以处理 assignWith 函数的自定义器参数
- 在多个处方组件中引入 drord_doctor_type 字典用于动态生成医嘱类型列表
- 修复手术类型(adviceType=6)的特殊处理逻辑,包括类型映射和字段过滤
- 调整后端医嘱保存服务中的类型分类逻辑,正确处理手术类型
- 更新数据库查询映射以支持手术类型的正确显示和数据传输
- 修复费用对话框和订单表单中的相关类型显示问题
2026-04-01 18:24:24 +08:00
Ranyunqiao
8a863b4ecb 106 入科选床界面的住院医生、主治医生、主任医生字段需按照医生维护的职称进行过滤 2026-04-01 16:47:27 +08:00
wangjian963
882d63249c refactor(检验申请): 重构检验申请单生成逻辑,由后端统一处理
- 移除前端生成申请单号的逻辑,改为后端在保存时自动生成
- 申请日期由后端统一处理,前端实时显示当前时间
- 优化金额计算逻辑,确保后端重新计算防止篡改
- 增加废号处理机制,记录生成但保存失败的申请单号
- 简化前端代码,移除不必要的检查逻辑
2026-04-01 16:37:32 +08:00
Ranyunqiao
6315ca5658 220 门诊医生站:新增耗材收费项目医嘱单价/总金额未显示正确的值 2026-04-01 15:25:08 +08:00
Ranyunqiao
9f802b67f0 313
检查项目设置-》套餐设置:折扣字段换算错误
2026-04-01 15:23:46 +08:00
6694ae52ba feat: 手术申请列表-手术单号移到申请日期之前(第一栏) 2026-04-01 14:00:58 +08:00
9491ceaa5d feat: 手术申请列表-手术单号支持点击查看详情 2026-04-01 13:36:44 +08:00
db9a70a99d feat: 手术申请列表-手术单号放第二栏并支持点击查看详情 2026-04-01 13:28:24 +08:00
Ranyunqiao
9105e687d6 98 门诊管理-》门诊划价:选项增加‘西药’和‘中成药’ 2026-04-01 13:14:46 +08:00
b1d6c6008e fix: doctorstation手术医嘱advice_type使用category_enum,advice_name支持surgeryName 2026-04-01 12:57:52 +08:00
6b9f9a107e fix: 手术医嘱类型显示修复 - SQL返回category_enum作为advice_type,前端添加手术类型选项 2026-04-01 12:45:15 +08:00
11a7f49162 fix: 手术医嘱therapy_enum默认为2(临时医嘱),避免被前端过滤 2026-04-01 12:16:36 +08:00
b4e5061b73 Merge remote-tracking branch 'origin/develop' into develop 2026-04-01 11:56:27 +08:00
f5a1ad7f3f fix: 手术医嘱advice_name从content_json解析surgeryName 2026-04-01 11:36:50 +08:00
eeac88b1d1 fix: Bug #318 历史数据修复脚本(Python+SQL) 2026-04-01 10:48:44 +08:00
1ab9b020c1 Merge branch 'develop' of https://gitea.gentronhealth.com/py/his into develop 2026-04-01 10:46:14 +08:00
3055518d2b Fix: 帮助文档打包失败 2026-04-01 10:46:05 +08:00
9f619ccdd4 fix: Bug #318 历史数据修复SQL脚本 2026-04-01 10:28:45 +08:00
df78ff29bd fix(build): 解决 lodash.template 中 assignWith 函数缺失问题
- 添加 JavaScript 脚本修复 lodash.template 模块中的 assignWith 问题
- 提供 Shell 脚本支持 Linux/Mac 系统的自动修复功能
- 实现 assignWith 函数的简单 polyfill 版本以确保兼容性
- 添加补丁检测机制防止重复修补同一文件
- 在构建前自动运行修复脚本确保依赖完整性
2026-04-01 10:03:38 +08:00
4d13acacc2 chore(deps): 添加 lodash 依赖包
- 在 package.json 中新增 lodash 依赖,版本为 ^4.17.21
- 更新依赖配置以支持工具函数库的引入
2026-04-01 09:35:42 +08:00
67573c1d9d fix: 添加诊断日志排查手术医嘱生成问题 2026-04-01 09:27:23 +08:00
b27d8a6703 fix: 修复门诊手术申请后未生成预收费明细记录的问题 (Bug #307)
- 修改 OutpatientChargeAppMapper.xml

- 在门诊收费查询SQL中增加对 cli_surgery 表的关联

- 支持手术申请生成的收费项目正确显示在门诊收费系统中
2026-04-01 09:17:41 +08:00
6f3d4272e6 fix: 添加手术医嘱生成日志,用于排查问题 2026-04-01 08:59:44 +08:00
6e5315fdd6 fix: 修复Bug #318 - 使用contentJson替代note字段存储手术信息 2026-03-31 17:52:17 +08:00
544d7ee95c Merge remote-tracking branch 'origin/develop' into develop 2026-03-31 17:38:20 +08:00
7f7f7d69f7 fix: 修复Bug #318 - 门诊医生站手术申请单自动生成手术医嘱(从descJson解析) 2026-03-31 17:37:15 +08:00
wangjian963
ae9a96822e fix(consultation): 限制会诊申请作废状态条件
修改会诊申请作废逻辑,仅允许新开和已提交状态可作废
前端界面同步调整作废按钮的禁用状态
后端增加状态校验防止非法操作
2026-03-31 17:30:16 +08:00
bbef0322a3 feat(surgicalschedule): 添加手术单号查询功能并优化收费状态 BUG#306
- 在手术申请查询界面添加手术单号输入框
- 将收费项目状态从草稿改为待收费状态
- 在请求表单DTO中添加手术单号字段
- 在数据库查询中关联手术安排表并添加手术单号过滤条件
- 添加筛选条件确保只查询未安排手术的申请记录
2026-03-31 17:18:09 +08:00
a8a205aa48 fix: 修复门诊医生站手术申请单保存后未生成手术医嘱 (#318)
- 在 RequestFormManageAppServiceImpl.saveRequestForm 方法中添加手术医嘱生成逻辑
- 当 typeCode 为 PROCEDURE(24) 时,额外生成 ServiceRequest 手术医嘱
- 同时生成对应的 ChargeItem 收费项目
- 医嘱状态设置为 DRAFT(待签发)
- 关联申请单的 prescriptionNo 处方号
2026-03-31 16:35:34 +08:00
c052ea7c39 Merge remote-tracking branch 'origin/develop' into develop 2026-03-31 16:10:42 +08:00
6accaa35c9 feat(surgicalschedule): 将手术安排日期查询改为日期范围选择 BUG#305
- 将前端日期选择器从单日期改为日期范围选择器
- 修改查询参数从 scheduleDate 改为 scheduleDateRange 数组
- 新增 scheduleDateStart 和 scheduleDateEnd 参数用于后端查询
- 在后端 DTO 中添加日期范围查询字段并配置格式化注解
- 更新 MyBatis XML 映射文件中的日期查询条件逻辑
- 实现前端日期范围到查询参数的转换处理逻辑
2026-03-31 16:10:34 +08:00
121 changed files with 9242 additions and 9900 deletions

View File

@@ -1,11 +1,15 @@
package com.core.framework.config; package com.core.framework.config;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.EnableAspectJAutoProxy;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone; import java.util.TimeZone;
/** /**
@@ -24,6 +28,14 @@ public class ApplicationConfig {
*/ */
@Bean @Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() { public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); return builder -> {
// 设置默认时区
builder.timeZone(TimeZone.getDefault());
// 设置日期格式为 yyyy/M/d HH:mm:ss支持多种格式反序列化
builder.simpleDateFormat("yyyy/M/d HH:mm:ss");
// 添加JavaTimeModule支持用于LocalDateTime
builder.modules(new JavaTimeModule());
builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy/M/d HH:mm:ss")));
};
} }
} }

View File

@@ -16,6 +16,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -123,6 +124,7 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (query == null) { if (query == null) {
query = new com.openhis.appointmentmanage.dto.TicketQueryDTO(); query = new com.openhis.appointmentmanage.dto.TicketQueryDTO();
} }
normalizeQueryStatus(query);
// 2. 构造 MyBatis 的分页对象 (传入前端给的当前页和每页条数) // 2. 构造 MyBatis 的分页对象 (传入前端给的当前页和每页条数)
com.baomidou.mybatisplus.extension.plugins.pagination.Page<com.openhis.appointmentmanage.domain.TicketSlotDTO> pageParam = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>( com.baomidou.mybatisplus.extension.plugins.pagination.Page<com.openhis.appointmentmanage.domain.TicketSlotDTO> pageParam = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(
@@ -140,42 +142,67 @@ public class TicketAppServiceImpl implements ITicketAppService {
// 基础字段映射 // 基础字段映射
dto.setSlot_id(raw.getSlotId()); dto.setSlot_id(raw.getSlotId());
dto.setSeqNo(raw.getSeqNo());
dto.setBusNo(String.valueOf(raw.getSlotId())); dto.setBusNo(String.valueOf(raw.getSlotId()));
dto.setDoctor(raw.getDoctor()); dto.setDoctor(raw.getDoctor());
dto.setDepartment(raw.getDepartmentName()); // 注意以前这里传成了ID导致前端出Bug现在修复成了真正的科室名 dto.setDepartment(raw.getDepartmentName()); // 注意以前这里传成了ID导致前端出Bug现在修复成了真正的科室名
dto.setFee(raw.getFee()); dto.setFee(raw.getFee());
dto.setPatientName(raw.getPatientName()); dto.setPatientName(raw.getPatientName());
dto.setPatientId(raw.getPatientId() != null ? String.valueOf(raw.getPatientId()) : null); dto.setPatientId(raw.getMedicalCard());
dto.setPhone(raw.getPhone()); dto.setPhone(raw.getPhone());
dto.setIdCard(raw.getIdCard());
dto.setDoctorId(raw.getDoctorId());
dto.setDepartmentId(raw.getDepartmentId());
dto.setRealPatientId(raw.getPatientId());
// 性别处理:直接读取优先级最高的订单性别字段 (SQL 已处理优先级)
if (raw.getPatientGender() != null) {
String pg = raw.getPatientGender().trim();
dto.setGender("1".equals(pg) ? "" : ("2".equals(pg) ? "" : "未知"));
} else {
dto.setGender("未知");
}
// 号源类型处理 (底层是1前端要的是expert)
if (raw.getRegType() != null && raw.getRegType() == 1) { if (raw.getRegType() != null && raw.getRegType() == 1) {
dto.setTicketType("expert"); dto.setTicketType("expert");
} else { } else {
dto.setTicketType("general"); dto.setTicketType("general");
} }
// 拼接就诊时间
if (raw.getScheduleDate() != null && raw.getExpectTime() != null) { if (raw.getScheduleDate() != null && raw.getExpectTime() != null) {
dto.setDateTime(raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString()); dto.setDateTime(raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString());
try { try {
dto.setAppointmentDate( String timeStr = raw.getAppointmentTime() != null ? raw.getAppointmentTime() : (raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString());
new java.text.SimpleDateFormat("yyyy-MM-dd").parse(raw.getScheduleDate().toString())); java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat(timeStr.length() > 10 ? "yyyy-MM-dd HH:mm" : "yyyy-MM-dd");
java.util.Date date = sdf.parse(timeStr);
dto.setAppointmentDate(date);
dto.setAppointmentTime(date);
} catch (Exception e) { } catch (Exception e) {
dto.setAppointmentDate(new java.util.Date()); dto.setAppointmentDate(new java.util.Date());
} }
} }
// 精准状态翻译把底层的1和2翻译回前端能懂的中文
if (Boolean.TRUE.equals(raw.getIsStopped())) { if (Boolean.TRUE.equals(raw.getIsStopped())) {
dto.setStatus("已停诊"); dto.setStatus("已停诊");
} else { } else {
Integer slotStatus = raw.getSlotStatus(); Integer slotStatus = raw.getSlotStatus();
if (slotStatus != null) { if (slotStatus != null) {
if (SlotStatus.BOOKED.equals(slotStatus)) { if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
dto.setStatus(AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus()) ? "已取号" : "已预约"); dto.setStatus("已取号");
} else if (SlotStatus.STOPPED.equals(slotStatus)) { } else if (SlotStatus.BOOKED.equals(slotStatus)) {
if (AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus())) {
dto.setStatus("已取号");
} else if (AppointmentOrderStatus.RETURNED.equals(raw.getOrderStatus())) {
dto.setStatus("已退号");
} else {
dto.setStatus("已预约");
}
} else if (SlotStatus.RETURNED.equals(slotStatus)) {
dto.setStatus("已退号");
} else if (SlotStatus.CANCELLED.equals(slotStatus)) {
dto.setStatus("已停诊"); dto.setStatus("已停诊");
} else if (SlotStatus.LOCKED.equals(slotStatus)) {
dto.setStatus("已锁定");
} else { } else {
dto.setStatus("未预约"); dto.setStatus("未预约");
} }
@@ -198,6 +225,62 @@ public class TicketAppServiceImpl implements ITicketAppService {
return R.ok(result); return R.ok(result);
} }
/**
* 统一状态入参,避免前端状态值大小写/中文/数字差异导致 SQL 条件失效后回全量数据
*/
private void normalizeQueryStatus(com.openhis.appointmentmanage.dto.TicketQueryDTO query) {
String rawStatus = query.getStatus();
if (rawStatus == null) {
return;
}
String normalized = rawStatus.trim();
if (normalized.isEmpty()) {
query.setStatus(null);
return;
}
String lower = normalized.toLowerCase(Locale.ROOT);
switch (lower) {
case "all":
case "全部":
query.setStatus("all");
break;
case "unbooked":
case "0":
case "未预约":
query.setStatus("unbooked");
break;
case "booked":
case "1":
case "已预约":
query.setStatus("booked");
break;
case "checked":
case "checkin":
case "checkedin":
case "2":
case "已取号":
query.setStatus("checked");
break;
case "cancelled":
case "canceled":
case "3":
case "已停诊":
case "已取消":
query.setStatus("cancelled");
break;
case "returned":
case "4":
case "5":
case "已退号":
query.setStatus("returned");
break;
default:
// 设置为 impossible 值,配合 mapper 的 otherwise 分支直接返回空
query.setStatus("__invalid__");
break;
}
}
@Override @Override
public R<?> listDoctorAvailability(com.openhis.appointmentmanage.dto.TicketQueryDTO query) { public R<?> listDoctorAvailability(com.openhis.appointmentmanage.dto.TicketQueryDTO query) {
if (query == null) { if (query == null) {
@@ -237,12 +320,13 @@ public class TicketAppServiceImpl implements ITicketAppService {
// --- 基础字段处理 --- // --- 基础字段处理 ---
// 注意:这里已经变成了极其舒服的 .getSlotId() 方法调用,告别魔鬼字符串! // 注意:这里已经变成了极其舒服的 .getSlotId() 方法调用,告别魔鬼字符串!
dto.setSlot_id(raw.getSlotId()); dto.setSlot_id(raw.getSlotId());
dto.setSeqNo(raw.getSeqNo());
dto.setBusNo(String.valueOf(raw.getSlotId())); // 暂时借用真实槽位ID做唯一流水号 dto.setBusNo(String.valueOf(raw.getSlotId())); // 暂时借用真实槽位ID做唯一流水号
dto.setDoctor(raw.getDoctor()); dto.setDoctor(raw.getDoctor());
dto.setDepartment(raw.getDepartmentName()); dto.setDepartment(raw.getDepartmentName());
dto.setFee(raw.getFee()); dto.setFee(raw.getFee());
dto.setPatientName(raw.getPatientName()); dto.setPatientName(raw.getPatientName());
dto.setPatientId(raw.getPatientId() != null ? String.valueOf(raw.getPatientId()) : null); dto.setPatientId(raw.getMedicalCard());
dto.setPhone(raw.getPhone()); dto.setPhone(raw.getPhone());
// --- 号源类型处理 (普通/专家) --- // --- 号源类型处理 (普通/专家) ---
@@ -258,9 +342,13 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (raw.getScheduleDate() != null && raw.getExpectTime() != null) { if (raw.getScheduleDate() != null && raw.getExpectTime() != null) {
dto.setDateTime(raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString()); dto.setDateTime(raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString());
try { try {
dto.setAppointmentDate( String timeStr = raw.getAppointmentTime() != null ? raw.getAppointmentTime() : (raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString());
new java.text.SimpleDateFormat("yyyy-MM-dd").parse(raw.getScheduleDate().toString())); java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat(timeStr.length() > 10 ? "yyyy-MM-dd HH:mm" : "yyyy-MM-dd");
java.util.Date date = sdf.parse(timeStr);
dto.setAppointmentDate(date);
dto.setAppointmentTime(date);
} catch (Exception e) { } catch (Exception e) {
log.error("时间解析失败", e);
dto.setAppointmentDate(new java.util.Date()); dto.setAppointmentDate(new java.util.Date());
} }
} }
@@ -273,10 +361,22 @@ public class TicketAppServiceImpl implements ITicketAppService {
// 第二关:看独立的细分槽位状态 (0: 可用, 1: 已预约, 2: 已取消...) // 第二关:看独立的细分槽位状态 (0: 可用, 1: 已预约, 2: 已取消...)
Integer slotStatus = raw.getSlotStatus(); Integer slotStatus = raw.getSlotStatus();
if (slotStatus != null) { if (slotStatus != null) {
if (SlotStatus.BOOKED.equals(slotStatus)) { if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
dto.setStatus(AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus()) ? "已取号" : "已预约"); dto.setStatus("已取号");
} else if (SlotStatus.STOPPED.equals(slotStatus)) { } else if (SlotStatus.BOOKED.equals(slotStatus)) {
dto.setStatus("已停诊"); // 视业务可改回已取消 if (AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus())) {
dto.setStatus("已取号");
} else if (AppointmentOrderStatus.RETURNED.equals(raw.getOrderStatus())) {
dto.setStatus("已退号");
} else {
dto.setStatus("已预约");
}
} else if (SlotStatus.RETURNED.equals(slotStatus)) {
dto.setStatus("已退号");
} else if (SlotStatus.CANCELLED.equals(slotStatus)) {
dto.setStatus("已停诊");
} else if (SlotStatus.LOCKED.equals(slotStatus)) {
dto.setStatus("已锁定");
} else { } else {
dto.setStatus("未预约"); dto.setStatus("未预约");
} }
@@ -355,14 +455,11 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (patient != null) { if (patient != null) {
Integer genderEnum = patient.getGenderEnum(); Integer genderEnum = patient.getGenderEnum();
if (genderEnum != null) { if (genderEnum != null) {
switch (genderEnum) { if (Integer.valueOf(1).equals(genderEnum)) {
case 1:
dto.setGender(""); dto.setGender("");
break; } else if (Integer.valueOf(2).equals(genderEnum)) {
case 2:
dto.setGender(""); dto.setGender("");
break; } else {
default:
dto.setGender("未知"); dto.setGender("未知");
} }
} }

View File

@@ -23,6 +23,11 @@ public class TicketDto {
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private Long slot_id; private Long slot_id;
/**
* 号源序号(对应 adm_schedule_slot.seq_no
*/
private Integer seqNo;
/** /**
* 号源编码 * 号源编码
*/ */
@@ -99,4 +104,15 @@ public class TicketDto {
*/ */
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private Long doctorId; private Long doctorId;
/**
* 真实患者ID数据库主键区别于 patientId 存的就诊卡号)
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long realPatientId;
/**
* 身份证号
*/
private String idCard;
} }

View File

@@ -119,6 +119,7 @@ public class OutpatientChargeAppServiceImpl implements IOutpatientChargeAppServi
= outpatientChargeAppMapper.selectEncounterPatientPrescription(encounterId, = outpatientChargeAppMapper.selectEncounterPatientPrescription(encounterId,
ChargeItemContext.ACTIVITY.getValue(), ChargeItemContext.MEDICATION.getValue(), ChargeItemContext.ACTIVITY.getValue(), ChargeItemContext.MEDICATION.getValue(),
ChargeItemContext.DEVICE.getValue(), ChargeItemContext.REGISTER.getValue(), ChargeItemContext.DEVICE.getValue(), ChargeItemContext.REGISTER.getValue(),
ChargeItemContext.WESTERN_MEDICINE.getValue(), ChargeItemContext.CHINESE_PATENT_MEDICINE.getValue(),
ChargeItemStatus.PLANNED.getValue(), ChargeItemStatus.BILLABLE.getValue(), ChargeItemStatus.PLANNED.getValue(), ChargeItemStatus.BILLABLE.getValue(),
ChargeItemStatus.BILLED.getValue(), ChargeItemStatus.REFUNDING.getValue(), ChargeItemStatus.BILLED.getValue(), ChargeItemStatus.REFUNDING.getValue(),
ChargeItemStatus.REFUNDED.getValue(), ChargeItemStatus.PART_REFUND.getValue(), ChargeItemStatus.REFUNDED.getValue(), ChargeItemStatus.PART_REFUND.getValue(),

View File

@@ -73,10 +73,10 @@ public class OutpatientPricingAppServiceImpl implements IOutpatientPricingAppSer
} else { } else {
adviceTypes = List.of(1, 2, 3); adviceTypes = List.of(1, 2, 3);
} }
// 门诊划价:不要强制 pricingFlag=1 参与过滤wor_activity_definition.pricing_flag 可能为 0 String categoryCode = adviceBaseDto != null ? adviceBaseDto.getCategoryCode() : null;
// 否则会导致诊疗项目(adviceType=3)查询结果为空 records=[] // 门诊划价:仅返回划价标记为“是”的项目
return iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId, null, return iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId, null,
organizationId, pageNo, pageSize, null, adviceTypes, null); organizationId, pageNo, pageSize, Whether.YES.getValue(), adviceTypes, null, categoryCode);
} }
} }

View File

@@ -22,6 +22,10 @@ import com.openhis.common.enums.ybenums.YbPayment;
import com.openhis.common.utils.EnumUtils; import com.openhis.common.utils.EnumUtils;
import com.openhis.common.utils.HisPageUtils; import com.openhis.common.utils.HisPageUtils;
import com.openhis.common.utils.HisQueryUtils; import com.openhis.common.utils.HisQueryUtils;
import com.openhis.appointmentmanage.mapper.SchedulePoolMapper;
import com.openhis.appointmentmanage.mapper.ScheduleSlotMapper;
import com.openhis.clinical.domain.Order;
import com.openhis.clinical.service.IOrderService;
import com.openhis.financial.domain.PaymentReconciliation; import com.openhis.financial.domain.PaymentReconciliation;
import com.openhis.financial.domain.RefundLog; import com.openhis.financial.domain.RefundLog;
import com.openhis.financial.service.IRefundLogService; import com.openhis.financial.service.IRefundLogService;
@@ -48,6 +52,7 @@ import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -97,6 +102,15 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
@Resource @Resource
IRefundLogService iRefundLogService; IRefundLogService iRefundLogService;
@Resource
IOrderService orderService;
@Resource
ScheduleSlotMapper scheduleSlotMapper;
@Resource
SchedulePoolMapper schedulePoolMapper;
/** /**
* 门诊挂号 - 查询患者信息 * 门诊挂号 - 查询患者信息
* *
@@ -291,6 +305,11 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
} }
} }
// 如果本次门诊挂号来自预约签到,同步把预约订单与号源槽位状态改为已退号
if (result != null && result.getCode() == 200) {
syncAppointmentReturnStatus(byId, cancelRegPaymentDto.getReason());
}
// 记录退号日志 // 记录退号日志
recordRefundLog(cancelRegPaymentDto, byId, result, paymentRecon); recordRefundLog(cancelRegPaymentDto, byId, result, paymentRecon);
@@ -399,6 +418,74 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
return R.ok("已取消挂号"); return R.ok("已取消挂号");
} }
/**
* 同步预约号源状态为已退号。
* 说明:
* 1) 门诊退号主流程不依赖该步骤成功与否,因此此方法内部异常仅记录日志,不向上抛出。
* 2) 通过患者、科室、日期以及状态筛选最近一条预约订单,尽量避免误匹配。
*/
private void syncAppointmentReturnStatus(Encounter encounter, String reason) {
if (encounter == null || encounter.getPatientId() == null) {
return;
}
try {
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounter.getPatientId())
.in(Order::getStatus, CommonConstants.AppointmentOrderStatus.BOOKED,
CommonConstants.AppointmentOrderStatus.CHECKED_IN)
.orderByDesc(Order::getUpdateTime)
.orderByDesc(Order::getCreateTime)
.last("LIMIT 1");
if (encounter.getOrganizationId() != null) {
queryWrapper.eq(Order::getDepartmentId, encounter.getOrganizationId());
}
if (encounter.getTenantId() != null) {
queryWrapper.eq(Order::getTenantId, encounter.getTenantId());
}
if (encounter.getCreateTime() != null) {
LocalDate encounterDate = encounter.getCreateTime().toInstant()
.atZone(ZoneId.systemDefault()).toLocalDate();
Date startOfDay = Date.from(encounterDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
Date nextDayStart = Date.from(encounterDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
queryWrapper.ge(Order::getAppointmentDate, startOfDay)
.lt(Order::getAppointmentDate, nextDayStart);
}
Order appointmentOrder = orderService.getOne(queryWrapper, false);
if (appointmentOrder == null) {
return;
}
Date now = new Date();
if (!CommonConstants.AppointmentOrderStatus.RETURNED.equals(appointmentOrder.getStatus())) {
Order updateOrder = new Order();
updateOrder.setId(appointmentOrder.getId());
updateOrder.setStatus(CommonConstants.AppointmentOrderStatus.RETURNED);
updateOrder.setCancelTime(now);
updateOrder.setCancelReason(
StringUtils.isNotEmpty(reason) ? reason : "门诊退号");
updateOrder.setUpdateTime(now);
orderService.updateById(updateOrder);
}
Long slotId = appointmentOrder.getSlotId();
if (slotId == null) {
return;
}
int slotRows = scheduleSlotMapper.updateSlotStatus(slotId, CommonConstants.SlotStatus.RETURNED);
if (slotRows > 0) {
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
if (poolId != null) {
schedulePoolMapper.refreshPoolStats(poolId);
}
}
} catch (Exception e) {
log.warn("同步预约号源已退号状态失败, encounterId={}", encounter.getId(), e);
}
}
/** /**
* 补打挂号 * 补打挂号
* 补打挂号不需要修改数据库,只需要返回成功即可,前端已有所有需要的数据用于打印 * 补打挂号不需要修改数据库,只需要返回成功即可,前端已有所有需要的数据用于打印

View File

@@ -61,7 +61,12 @@ public class OutpatientPricingController {
@RequestParam(value = "locationId", required = false) Long locationId, @RequestParam(value = "locationId", required = false) Long locationId,
@RequestParam(value = "organizationId") Long organizationId, @RequestParam(value = "organizationId") Long organizationId,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo, @RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) { @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
@RequestParam(value = "categoryCode", required = false) String categoryCode) {
// 将 categoryCode 设置到 adviceBaseDto 中
if (categoryCode != null && !categoryCode.isEmpty()) {
adviceBaseDto.setCategoryCode(categoryCode);
}
return R.ok(iOutpatientPricingAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId, organizationId, return R.ok(iOutpatientPricingAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId, organizationId,
pageNo, pageSize)); pageNo, pageSize));
} }

View File

@@ -42,6 +42,8 @@ public interface OutpatientChargeAppMapper {
* @param medication 药品 * @param medication 药品
* @param device 耗材 * @param device 耗材
* @param register 挂号费 * @param register 挂号费
* @param westernMedicine 西药
* @param chinesePatentMedicine 中成药
* @param planned 收费状态:待收费 * @param planned 收费状态:待收费
* @param billable 收费状态:待结算 * @param billable 收费状态:待结算
* @param billed 收费状态:已结算 * @param billed 收费状态:已结算
@@ -53,7 +55,9 @@ public interface OutpatientChargeAppMapper {
*/ */
List<EncounterPatientPrescriptionDto> selectEncounterPatientPrescription(@Param("encounterId") Long encounterId, List<EncounterPatientPrescriptionDto> selectEncounterPatientPrescription(@Param("encounterId") Long encounterId,
@Param("activity") Integer activity, @Param("medication") Integer medication, @Param("device") Integer device, @Param("activity") Integer activity, @Param("medication") Integer medication, @Param("device") Integer device,
@Param("register") Integer register, @Param("planned") Integer planned, @Param("billable") Integer billable, @Param("register") Integer register, @Param("westernMedicine") Integer westernMedicine,
@Param("chinesePatentMedicine") Integer chinesePatentMedicine,
@Param("planned") Integer planned, @Param("billable") Integer billable,
@Param("billed") Integer billed, @Param("refunding") Integer refunding, @Param("refunded") Integer refunded, @Param("billed") Integer billed, @Param("refunding") Integer refunding, @Param("refunded") Integer refunded,
@Param("partRefund") Integer partRefund, @Param("worDeviceRequest") String worDeviceRequest); @Param("partRefund") Integer partRefund, @Param("worDeviceRequest") String worDeviceRequest);

View File

@@ -343,11 +343,28 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
serviceRequest.setEncounterId(surgeryDto.getEncounterId()); // 就诊id serviceRequest.setEncounterId(surgeryDto.getEncounterId()); // 就诊id
serviceRequest.setAuthoredTime(curDate); // 请求签发时间 serviceRequest.setAuthoredTime(curDate); // 请求签发时间
serviceRequest.setOrgId(orgId); // 执行科室 serviceRequest.setOrgId(orgId); // 执行科室
// 🔧 BugFix#318: 设置 contentJson包含手术名称
Map<String, String> serviceContentMap = new HashMap<>();
String surgeryNameFromDto = surgeryDto.getSurgeryName();
String surgeryCodeFromDto = surgeryDto.getSurgeryCode();
log.info("【DEBUG】surgeryName from DTO: {}", surgeryNameFromDto);
log.info("【DEBUG】surgeryCode from DTO: {}", surgeryCodeFromDto);
serviceContentMap.put("surgeryName", surgeryNameFromDto != null ? surgeryNameFromDto : "");
serviceContentMap.put("surgeryCode", surgeryCodeFromDto != null ? surgeryCodeFromDto : "");
try {
String contentJson = new ObjectMapper().writeValueAsString(serviceContentMap);
log.info("【DEBUG】Setting contentJson: {}", contentJson);
serviceRequest.setContentJson(contentJson);
} catch (JsonProcessingException e) {
log.error("【DEBUG】设置手术医嘱 contentJson 失败", e);
}
serviceRequestService.save(serviceRequest); serviceRequestService.save(serviceRequest);
log.info("【DEBUG】Saved serviceRequest with ID: {}, contentJson: {}",
serviceRequest.getId(), serviceRequest.getContentJson());
// 生成收费项目 // 生成收费项目
ChargeItem chargeItem = new ChargeItem(); ChargeItem chargeItem = new ChargeItem();
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态 chargeItem.setStatusEnum(ChargeItemStatus.PLANNED.getValue()); // 收费状态:待收费
chargeItem.setBusNo("CI" + serviceRequest.getBusNo()); chargeItem.setBusNo("CI" + serviceRequest.getBusNo());
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源 chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
chargeItem.setPatientId(surgeryDto.getPatientId()); // 患者 chargeItem.setPatientId(surgeryDto.getPatientId()); // 患者

View File

@@ -371,7 +371,7 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
index, // 序号从1开始 index, // 序号从1开始
schedule.getOrgName() != null ? schedule.getOrgName() : "", schedule.getOrgName() != null ? schedule.getOrgName() : "",
schedule.getPatientName() != null ? schedule.getPatientName() : "", schedule.getPatientName() != null ? schedule.getPatientName() : "",
schedule.getVisitId() != null ? schedule.getVisitId().toString() : "", schedule.getIdentifierNo() != null ? schedule.getIdentifierNo() : "",
schedule.getOperCode() != null ? schedule.getOperCode() : "", schedule.getOperCode() != null ? schedule.getOperCode() : "",
schedule.getOperName() != null ? schedule.getOperName() : "", schedule.getOperName() != null ? schedule.getOperName() : "",
schedule.getApplyDeptName() != null ? schedule.getApplyDeptName() : "", schedule.getApplyDeptName() != null ? schedule.getApplyDeptName() : "",

View File

@@ -24,6 +24,11 @@ public class OpCreateScheduleDto {
*/ */
private Long visitId; private Long visitId;
/**
* 就诊卡号
*/
private String identifierNo;
/** /**
* 手术编码 * 手术编码
*/ */

View File

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import com.openhis.surgicalschedule.domain.OpSchedule; import com.openhis.surgicalschedule.domain.OpSchedule;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate; import java.time.LocalDate;
@@ -18,6 +19,20 @@ import java.time.LocalDate;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class OpScheduleDto extends OpSchedule { public class OpScheduleDto extends OpSchedule {
/**
* 手术安排日期开始(查询用)
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate scheduleDateStart;
/**
* 手术安排日期结束(查询用)
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate scheduleDateEnd;
/** /**
* 患者姓名 * 患者姓名
*/ */
@@ -28,6 +43,11 @@ public class OpScheduleDto extends OpSchedule {
*/ */
private Long encounterId; private Long encounterId;
/**
* 就诊卡号
*/
private String patientCardNo;
/** /**
* 性别 * 性别
*/ */

View File

@@ -45,6 +45,9 @@ public class SurgeryDto {
/** 就诊流水号 */ /** 就诊流水号 */
private String encounterNo; private String encounterNo;
/** 就诊卡号 */
private String patientCardNo;
/** 申请医生ID */ /** 申请医生ID */
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private Long applyDoctorId; private Long applyDoctorId;

View File

@@ -465,7 +465,13 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
updateServiceRequestStatus(entity.getOrderId(), RequestStatus.DRAFT.getValue()); updateServiceRequestStatus(entity.getOrderId(), RequestStatus.DRAFT.getValue());
} else { } else {
// 作废:状态改为"已取消" // 作废:状态校验 - 已确认(20)、已签名(30)、已完成(40) 状态禁止作废
ConsultationStatusEnum currentStatus = ConsultationStatusEnum.getByCode(entity.getConsultationStatus());
if (currentStatus != null && !currentStatus.canCancel()) {
throw new IllegalArgumentException("当前状态【" + currentStatus.getDescription() + "】不允许作废,只有新开或已提交状态的会诊申请才能作废");
}
// 将状态改为"已取消"
entity.setConsultationStatus(CANCELLED.getCode()); entity.setConsultationStatus(CANCELLED.getCode());
entity.setCancelReason(cancelReason); entity.setCancelReason(cancelReason);
entity.setCancelNatureDate(new Date()); entity.setCancelNatureDate(new Date());

View File

@@ -76,10 +76,12 @@ public enum ConsultationStatusEnum {
} }
/** /**
* 判断是否可以取消 * 判断是否可以取消/作废
* 只有新开(0)和已提交(10)状态可以作废
* 已确认(20)、已签名(30)、已完成(40)状态禁止作废
*/ */
public boolean canCancel() { public boolean canCancel() {
return this == NEW || this == SUBMITTED || this == CONFIRMED; return this == NEW || this == SUBMITTED;
} }
} }

View File

@@ -78,7 +78,6 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
private IOperationRecordService operationRecordService; private IOperationRecordService operationRecordService;
@Resource @Resource
private IServiceRequestService serviceRequestService; private IServiceRequestService serviceRequestService;
/** /**
* 诊疗目录初期查询 * 诊疗目录初期查询
* *
@@ -240,9 +239,8 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
DiagnosisTreatmentSelParam.setPricingFlag(pricingFlagValue); DiagnosisTreatmentSelParam.setPricingFlag(pricingFlagValue);
} }
// 分页查询
IPage<DiagnosisTreatmentDto> diseaseTreatmentPage IPage<DiagnosisTreatmentDto> diseaseTreatmentPage
= activityDefinitionManageMapper.getDiseaseTreatmentPage(new Page<DiagnosisTreatmentDto>(pageNo, pageSize), queryWrapper); = activityDefinitionManageMapper.getDiseaseTreatmentPage(new Page<>(pageNo, pageSize), queryWrapper);
diseaseTreatmentPage.getRecords().forEach(e -> { diseaseTreatmentPage.getRecords().forEach(e -> {
// 医保标记枚举类回显赋值 // 医保标记枚举类回显赋值
@@ -447,24 +445,17 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
*/ */
@Override @Override
public R<?> editDiseaseTreatmentStop(List<Long> ids) { public R<?> editDiseaseTreatmentStop(List<Long> ids) {
List<ActivityDefinition> actList = new CopyOnWriteArrayList<>();
List<ActivityDefinition> ActivityDefinitionList = new CopyOnWriteArrayList<>(); for (Long id : ids) {
ActivityDefinition act = new ActivityDefinition();
// 取得更新值 act.setId(id);
for (Long detail : ids) { act.setStatusEnum(PublicationStatus.RETIRED.getValue());
ActivityDefinition ActivityDefinition = new ActivityDefinition(); actList.add(act);
ActivityDefinition.setId(detail);
ActivityDefinition.setStatusEnum(PublicationStatus.RETIRED.getValue());
ActivityDefinitionList.add(ActivityDefinition);
} }
// 插入操作记录
operationRecordService.addIdsOperationRecord(DbOpType.STOP.getCode(), operationRecordService.addIdsOperationRecord(DbOpType.STOP.getCode(),
CommonConstants.TableName.WOR_ACTIVITY_DEFINITION, ids); CommonConstants.TableName.WOR_ACTIVITY_DEFINITION, ids);
// 更新诊疗信息 activityDefinitionService.updateBatchById(actList);
return activityDefinitionService.updateBatchById(ActivityDefinitionList) return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"\u8bca\u7597\u76ee\u5f55"}));
? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"诊疗目录"}))
: R.fail(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00007, null));
} }
/** /**
@@ -475,24 +466,17 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
*/ */
@Override @Override
public R<?> editDiseaseTreatmentStart(List<Long> ids) { public R<?> editDiseaseTreatmentStart(List<Long> ids) {
List<ActivityDefinition> actList = new CopyOnWriteArrayList<>();
List<ActivityDefinition> ActivityDefinitionList = new CopyOnWriteArrayList<>(); for (Long id : ids) {
ActivityDefinition act = new ActivityDefinition();
// 取得更新值 act.setId(id);
for (Long detail : ids) { act.setStatusEnum(PublicationStatus.ACTIVE.getValue());
ActivityDefinition ActivityDefinition = new ActivityDefinition(); actList.add(act);
ActivityDefinition.setId(detail);
ActivityDefinition.setStatusEnum(PublicationStatus.ACTIVE.getValue());
ActivityDefinitionList.add(ActivityDefinition);
} }
// 插入操作记录
operationRecordService.addIdsOperationRecord(DbOpType.START.getCode(), operationRecordService.addIdsOperationRecord(DbOpType.START.getCode(),
CommonConstants.TableName.WOR_ACTIVITY_DEFINITION, ids); CommonConstants.TableName.WOR_ACTIVITY_DEFINITION, ids);
// 更新诊疗信息 activityDefinitionService.updateBatchById(actList);
return activityDefinitionService.updateBatchById(ActivityDefinitionList) return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"诊疗目录"}));
? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"诊疗目录"}))
: R.fail(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00007, null));
} }
/** /**

View File

@@ -0,0 +1,51 @@
package com.openhis.web.datadictionary.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 检验项目定义管理 Mapper操作 lab_activity_definition 表)
*/
@Repository
public interface LabActivityDefinitionManageMapper {
/**
* 检验项目分页查询
*
* @param page 分页参数
* @param queryWrapper 查询条件
* @return 分页结果
*/
IPage<DiagnosisTreatmentDto> getLabActivityDefinitionPage(
@Param("page") Page<DiagnosisTreatmentDto> page,
@Param(Constants.WRAPPER) QueryWrapper<DiagnosisTreatmentDto> queryWrapper);
/**
* 检验项目详情
*
* @param id 项目ID
* @param tenantId 租户ID
* @return 详情
*/
DiagnosisTreatmentDto getLabActivityDefinitionOne(@Param("id") Long id, @Param("tenantId") Integer tenantId);
/**
* 检验项目下拉列表(轻量级)
*
* @param statusEnum 状态
* @param tenantId 租户ID
* @param searchKey 搜索关键词(可选)
* @return 列表
*/
List<DiagnosisTreatmentDto> getLabActivityDefinitionSimpleList(
@Param("statusEnum") Integer statusEnum,
@Param("tenantId") Integer tenantId,
@Param("searchKey") String searchKey);
}

View File

@@ -31,7 +31,7 @@ public interface IDoctorStationAdviceAppService {
*/ */
IPage<AdviceBaseDto> getAdviceBaseInfo(AdviceBaseDto adviceBaseDto, String searchKey, Long locationId, IPage<AdviceBaseDto> getAdviceBaseInfo(AdviceBaseDto adviceBaseDto, String searchKey, Long locationId,
List<Long> adviceDefinitionIdParamList, Long organizationId, Integer pageNo, Integer pageSize, List<Long> adviceDefinitionIdParamList, Long organizationId, Integer pageNo, Integer pageSize,
Integer pricingFlag, List<Integer> adviceTypes, String orderPricing); Integer pricingFlag, List<Integer> adviceTypes, String orderPricing, String categoryCode);
/** /**
* 查询医嘱绑定信息 * 查询医嘱绑定信息

View File

@@ -19,11 +19,4 @@ public interface IDoctorStationInspectionLabApplyService {
* @return 删除结果 * @return 删除结果
*/ */
R<?> deleteInspectionLabApply(String applyNo); R<?> deleteInspectionLabApply(String applyNo);
/**
* 生成检验申请单号
* 规则LS + YYYYMMDD + 5位流水号每日从1开始递增
* @return 申请单号
*/
String generateApplyNo();
} }

View File

@@ -26,6 +26,7 @@ import com.openhis.common.constant.PromptMsgConstant;
import com.openhis.common.enums.*; import com.openhis.common.enums.*;
import com.openhis.common.utils.EnumUtils; import com.openhis.common.utils.EnumUtils;
import com.openhis.common.utils.HisQueryUtils; import com.openhis.common.utils.HisQueryUtils;
import com.openhis.medication.domain.MedicationDispense;
import com.openhis.medication.domain.MedicationRequest; import com.openhis.medication.domain.MedicationRequest;
import com.openhis.medication.service.IMedicationDispenseService; import com.openhis.medication.service.IMedicationDispenseService;
import com.openhis.medication.service.IMedicationRequestService; import com.openhis.medication.service.IMedicationRequestService;
@@ -44,6 +45,8 @@ import com.openhis.workflow.service.IActivityDefinitionService;
import com.openhis.workflow.service.IDeviceDispenseService; import com.openhis.workflow.service.IDeviceDispenseService;
import com.openhis.workflow.service.IDeviceRequestService; import com.openhis.workflow.service.IDeviceRequestService;
import com.openhis.workflow.service.IServiceRequestService; import com.openhis.workflow.service.IServiceRequestService;
import com.openhis.workflow.domain.InventoryItem;
import com.openhis.workflow.service.IInventoryItemService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -111,6 +114,9 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
@Resource @Resource
IEncounterService iEncounterService; IEncounterService iEncounterService;
@Resource
IInventoryItemService inventoryItemService;
// 缓存 key 前缀 // 缓存 key 前缀
private static final String ADVICE_BASE_INFO_CACHE_PREFIX = "advice:base:info:"; private static final String ADVICE_BASE_INFO_CACHE_PREFIX = "advice:base:info:";
// 缓存过期时间(小时) // 缓存过期时间(小时)
@@ -135,7 +141,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
@Override @Override
public IPage<AdviceBaseDto> getAdviceBaseInfo(AdviceBaseDto adviceBaseDto, String searchKey, Long locationId, public IPage<AdviceBaseDto> getAdviceBaseInfo(AdviceBaseDto adviceBaseDto, String searchKey, Long locationId,
List<Long> adviceDefinitionIdParamList, Long organizationId, Integer pageNo, Integer pageSize, List<Long> adviceDefinitionIdParamList, Long organizationId, Integer pageNo, Integer pageSize,
Integer pricingFlag, List<Integer> adviceTypes, String orderPricing) { Integer pricingFlag, List<Integer> adviceTypes, String orderPricing, String categoryCode) {
// 生成缓存键处理可能的null值 // 生成缓存键处理可能的null值
String safeSearchKey = searchKey != null ? searchKey : ""; String safeSearchKey = searchKey != null ? searchKey : "";
@@ -203,7 +209,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
new Page<>(pageNo, pageSize), PublicationStatus.ACTIVE.getValue(), organizationId, new Page<>(pageNo, pageSize), PublicationStatus.ACTIVE.getValue(), organizationId,
CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION, CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION,
CommonConstants.TableName.WOR_ACTIVITY_DEFINITION, pricingFlag, adviceDefinitionIdParamList, CommonConstants.TableName.WOR_ACTIVITY_DEFINITION, pricingFlag, adviceDefinitionIdParamList,
adviceTypes, searchKey, adviceTypes, searchKey, categoryCode,
queryWrapper); queryWrapper);
List<AdviceBaseDto> adviceBaseDtoList = adviceBaseInfo.getRecords(); List<AdviceBaseDto> adviceBaseDtoList = adviceBaseInfo.getRecords();
@@ -564,13 +570,15 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 耗材前端adviceType=4后端ItemType.DEVICE=2 // 耗材前端adviceType=4后端ItemType.DEVICE=2
List<AdviceSaveDto> deviceList = adviceSaveList.stream() List<AdviceSaveDto> deviceList = adviceSaveList.stream()
.filter(e -> ItemType.DEVICE.getValue().equals(e.getAdviceType()) .filter(e -> ItemType.DEVICE.getValue().equals(e.getAdviceType())
|| e.getAdviceType() == 4) // 🔧 BugFix: 前端耗材类型值为4 || e.getAdviceType() == 4) // 前端耗材类型值为4
.collect(Collectors.toList()); .collect(Collectors.toList());
// 诊疗活动包括普通诊疗前端adviceType=3会诊前端adviceType=5
// 诊疗活动前端adviceType=3诊疗、adviceType=5会诊、adviceType=6手术
List<AdviceSaveDto> activityList = adviceSaveList.stream() List<AdviceSaveDto> activityList = adviceSaveList.stream()
.filter(e -> ItemType.ACTIVITY.getValue().equals(e.getAdviceType()) .filter(e -> ItemType.ACTIVITY.getValue().equals(e.getAdviceType())
|| e.getAdviceType() == 3 // 🔧 BugFix: 前端诊疗类型值为3 || e.getAdviceType() == 3 // 前端诊疗类型值为3
|| e.getAdviceType() == 5) // 🔧 BugFix: 前端会诊类型值为5 || e.getAdviceType() == 5 // 前端会诊类型值为5
|| ItemType.SURGERY.getValue().equals(e.getAdviceType())) // 🔧 BugFix#318: 手术类型值为6
.collect(Collectors.toList()); .collect(Collectors.toList());
// 🔍 Debug日志: 记录分类结果 // 🔍 Debug日志: 记录分类结果
@@ -599,11 +607,12 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
iDeviceDispenseService.deleteDeviceDispense(adviceSaveDto.getRequestId()); iDeviceDispenseService.deleteDeviceDispense(adviceSaveDto.getRequestId());
} }
// 🔧 Bug Fix: 跳过耗材的库存校验(耗材的库存校验逻辑不同) // 🔧 Bug Fix: 跳过耗材、诊疗、手术的库存校验
List<AdviceSaveDto> needCheckList = adviceSaveList.stream() List<AdviceSaveDto> needCheckList = adviceSaveList.stream()
.filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType()) .filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType())
&& !ItemType.ACTIVITY.getValue().equals(e.getAdviceType()) && !ItemType.ACTIVITY.getValue().equals(e.getAdviceType())
&& !ItemType.DEVICE.getValue().equals(e.getAdviceType())) // 排除耗材 && !ItemType.DEVICE.getValue().equals(e.getAdviceType())
&& !ItemType.SURGERY.getValue().equals(e.getAdviceType())) // 🔧 BugFix#318: 排除手术类型
.collect(Collectors.toList()); .collect(Collectors.toList());
// 校验库存 // 校验库存
String tipRes = adviceUtils.checkInventory(needCheckList); String tipRes = adviceUtils.checkInventory(needCheckList);
@@ -929,7 +938,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
deviceAdviceDto.setAdviceTableName(CommonConstants.TableName.ADM_DEVICE_DEFINITION); deviceAdviceDto.setAdviceTableName(CommonConstants.TableName.ADM_DEVICE_DEFINITION);
IPage<AdviceBaseDto> devicePage = getAdviceBaseInfo(deviceAdviceDto, null, null, null, IPage<AdviceBaseDto> devicePage = getAdviceBaseInfo(deviceAdviceDto, null, null, null,
adviceSaveDto.getFounderOrgId(), 1, 1, Whether.NO.getValue(), adviceSaveDto.getFounderOrgId(), 1, 1, Whether.NO.getValue(),
List.of(ItemType.DEVICE.getValue()), null); List.of(ItemType.DEVICE.getValue()), null, null);
if (devicePage == null || devicePage.getRecords().isEmpty()) { if (devicePage == null || devicePage.getRecords().isEmpty()) {
log.warn("无法找到耗材定价信息: deviceDefId={}", boundDevice.getDevActId()); log.warn("无法找到耗材定价信息: deviceDefId={}", boundDevice.getDevActId());
@@ -1192,6 +1201,47 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
chargeItem.setServiceId(deviceRequest.getId()); // 医疗服务ID chargeItem.setServiceId(deviceRequest.getId()); // 医疗服务ID
chargeItem.setProductTable(adviceSaveDto.getAdviceTableName());// 产品所在表 chargeItem.setProductTable(adviceSaveDto.getAdviceTableName());// 产品所在表
chargeItem.setProductId(adviceSaveDto.getAdviceDefinitionId());// 收费项id chargeItem.setProductId(adviceSaveDto.getAdviceDefinitionId());// 收费项id
// 🔧 Bug Fix: 如果 definitionId 或 definitionDetailId 为 null从定价信息中获取
if (chargeItem.getDefinitionId() == null || chargeItem.getDefDetailId() == null) {
log.warn("耗材的 definitionId 或 definitionDetailId 为 null尝试从定价信息中获取: deviceDefId={}",
adviceSaveDto.getAdviceDefinitionId());
// 查询耗材定价信息
IPage<AdviceBaseDto> devicePage = doctorStationAdviceAppMapper.getAdviceBaseInfo(
new Page<>(1, 1),
PublicationStatus.ACTIVE.getValue(),
orgId,
CommonConstants.TableName.ADM_DEVICE_DEFINITION,
null,
null,
null,
Arrays.asList(adviceSaveDto.getAdviceDefinitionId()),
null,
null,
null,
null);
if (devicePage != null && !devicePage.getRecords().isEmpty()) {
AdviceBaseDto deviceBaseInfo = devicePage.getRecords().get(0);
if (deviceBaseInfo.getPriceList() != null && !deviceBaseInfo.getPriceList().isEmpty()) {
AdvicePriceDto devicePrice = deviceBaseInfo.getPriceList().get(0);
if (chargeItem.getDefinitionId() == null) {
chargeItem.setDefinitionId(devicePrice.getDefinitionId());
log.info("从定价信息中获取 definitionId: {}", devicePrice.getDefinitionId());
}
if (chargeItem.getDefDetailId() == null) {
chargeItem.setDefDetailId(devicePrice.getDefinitionDetailId());
log.info("从定价信息中获取 definitionDetailId: {}", devicePrice.getDefinitionDetailId());
}
}
}
}
// 🔧 Bug Fix: 确保定义ID不为null
if (chargeItem.getDefinitionId() == null) {
log.error("无法获取耗材的 definitionId: deviceDefId={}", adviceSaveDto.getAdviceDefinitionId());
throw new ServiceException("无法获取耗材的定价信息,请联系管理员");
}
// 🔧 Bug Fix: 如果accountId为null从就诊中获取账户ID如果没有则自动创建 // 🔧 Bug Fix: 如果accountId为null从就诊中获取账户ID如果没有则自动创建
Long accountId = adviceSaveDto.getAccountId(); Long accountId = adviceSaveDto.getAccountId();
if (accountId == null) { if (accountId == null) {
@@ -1425,26 +1475,26 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 只有在签发时 // 只有在签发时
if (is_sign) { if (is_sign) {
// 发送跨系统申请 // 发送跨系统申请 - 已注释项目未使用LIS/PACS系统
adviceSaveDto.setRequestId(serviceRequest.getId()); // adviceSaveDto.setRequestId(serviceRequest.getId());
try { // try {
// 查询诊疗定义 // // 查询诊疗定义
ActivityDefinition activityDefinition // ActivityDefinition activityDefinition
= iActivityDefinitionService.getById(adviceSaveDto.getAdviceDefinitionId()); // = iActivityDefinitionService.getById(adviceSaveDto.getAdviceDefinitionId());
if (activityDefinition != null) { // if (activityDefinition != null) {
// 检验 或 检查 // // 检验 或 检查
if (ActivityType.PROOF.getValue().equals(activityDefinition.getTypeEnum()) // if (ActivityType.PROOF.getValue().equals(activityDefinition.getTypeEnum())
|| ActivityType.TEST.getValue().equals(activityDefinition.getTypeEnum())) { // || ActivityType.TEST.getValue().equals(activityDefinition.getTypeEnum())) {
doctorStationSendApplyUtil.sendCrossSystemApply(adviceSaveDto, organizationId, curDate); // doctorStationSendApplyUtil.sendCrossSystemApply(adviceSaveDto, organizationId, curDate);
} // }
} // }
} catch (Exception e) { // } catch (Exception e) {
if (!Whether.YES.getCode() // if (!Whether.YES.getCode()
.equals(TenantOptionUtil.getOptionContent(TenantOptionDict.LIS_PACS_ERROR_IGNORE))) { // .equals(TenantOptionUtil.getOptionContent(TenantOptionDict.LIS_PACS_ERROR_IGNORE))) {
throw e; // throw e;
} // }
log.error(e.getMessage(), e); // log.error(e.getMessage(), e);
} // }
} }
} }
} }
@@ -1509,15 +1559,60 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
iChargeItemService.updatePaymentStatus(chargeItemIdList, ChargeItemStatus.DRAFT.getValue()); iChargeItemService.updatePaymentStatus(chargeItemIdList, ChargeItemStatus.DRAFT.getValue());
} }
// 🔧 BugFix: 直接对所有requestId进行作废操作 // 🔧 新增:签退时回滚库存
log.info("BugFix#219: signOffAdvice - 作废所有请求, requestIdList={}", requestIdList); // 查询已发放的药品记录(用于回滚库存)
List<MedicationDispense> dispensedList = iMedicationDispenseService.list(
new LambdaQueryWrapper<MedicationDispense>()
.in(MedicationDispense::getMedReqId, requestIdList)
.eq(MedicationDispense::getStatusEnum, DispenseStatus.COMPLETED.getValue())
);
// 尝试作废药品请求(只有存在的才会更新) if (dispensedList != null && !dispensedList.isEmpty()) {
iMedicationRequestService.updateCancelledStatusBatch(requestIdList, null, null); // 需要回滚的库存列表
// 尝试作废耗材请求(只有存在的才会更新) List<InventoryItem> inventoryUpdateList = new ArrayList<>();
iDeviceRequestService.updateCancelledStatusBatch(requestIdList);
// 尝试作废诊疗请求(只有存在的才会更新) for (MedicationDispense dispense : dispensedList) {
iServiceRequestService.updateCancelledStatusBatch(requestIdList); // 查询对应的库存记录根据批号和药品ID
if (dispense.getMedicationId() != null && dispense.getLotNumber() != null) {
InventoryItem inventoryItem = inventoryItemService.getOne(
new LambdaQueryWrapper<InventoryItem>()
.eq(InventoryItem::getItemId, dispense.getMedicationId())
.eq(InventoryItem::getLotNumber, dispense.getLotNumber())
);
if (inventoryItem != null) {
// 计算回滚后的数量(加上已发放的数量)
BigDecimal currentQuantity = inventoryItem.getQuantity() != null ? inventoryItem.getQuantity() : BigDecimal.ZERO;
BigDecimal dispenseQuantity = dispense.getQuantity() != null ? dispense.getQuantity() : BigDecimal.ZERO;
inventoryUpdateList.add(new InventoryItem()
.setId(inventoryItem.getId())
.setQuantity(currentQuantity.add(dispenseQuantity))
);
}
}
// 更新发药记录状态为已退药
dispense.setStatusEnum(DispenseStatus.RETURNED.getValue());
}
// 批量更新库存(回滚数量)
if (!inventoryUpdateList.isEmpty()) {
inventoryItemService.updateBatchById(inventoryUpdateList);
}
// 更新发药记录状态
iMedicationDispenseService.updateBatchById(dispensedList);
}
// 🔧 BugFix: 直接对所有requestId进行签退操作将状态改为待签发
log.info("BugFix: signOffAdvice - 签退所有请求,状态改为待签发, requestIdList={}", requestIdList);
// 尝试签退药品请求(只有存在的才会更新)
iMedicationRequestService.updateDraftStatusBatch(requestIdList, null, null);
// 尝试签退耗材请求(只有存在的才会更新)
iDeviceRequestService.updateDraftStatusBatch(requestIdList);
// 尝试签退诊疗请求(只有存在的才会更新)
iServiceRequestService.updateDraftStatusBatch(requestIdList);
log.info("BugFix#219: signOffAdvice - 所有请求作废完成"); log.info("BugFix#219: signOffAdvice - 所有请求作废完成");

View File

@@ -369,7 +369,7 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
adviceBaseDto.setAdviceType(1); // 医嘱类型为药品 adviceBaseDto.setAdviceType(1); // 医嘱类型为药品
adviceBaseDto.setCategoryCode(MedCategoryCode.CHINESE_HERBAL_MEDICINE.getValue());// 中草药 adviceBaseDto.setCategoryCode(MedCategoryCode.CHINESE_HERBAL_MEDICINE.getValue());// 中草药
return iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId, return iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId,
adviceDefinitionIdParamList, organizationId, pageNo, pageSize, pricingFlag, List.of(1, 2, 3), null); adviceDefinitionIdParamList, organizationId, pageNo, pageSize, pricingFlag, List.of(1, 2, 3), null, null);
} }
/** /**
@@ -613,7 +613,7 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
// 对应的诊疗医嘱信息 // 对应的诊疗医嘱信息
AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null, AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null,
null, null, organizationId, 1, 1, Whether.NO.getValue(), List.of(3), null).getRecords().get(0); null, null, organizationId, 1, 1, Whether.NO.getValue(), List.of(3), null, null).getRecords().get(0);
if (activityAdviceBaseDto != null) { if (activityAdviceBaseDto != null) {
// 费用定价 // 费用定价
AdvicePriceDto advicePriceDto = activityAdviceBaseDto.getPriceList().get(0); AdvicePriceDto advicePriceDto = activityAdviceBaseDto.getPriceList().get(0);

View File

@@ -273,12 +273,28 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
*/ */
@Override @Override
public R<?> saveDoctorDiagnosisNew(SaveDiagnosisParam saveDiagnosisParam) { public R<?> saveDoctorDiagnosisNew(SaveDiagnosisParam saveDiagnosisParam) {
// 参数校验:确保诊断列表不为空
if (saveDiagnosisParam == null) {
return R.fail(MessageUtils.message(PromptMsgConstant.Common.M00009, new Object[] { "保存诊断参数" }));
}
// 患者id // 患者id
Long patientId = saveDiagnosisParam.getPatientId(); Long patientId = saveDiagnosisParam.getPatientId();
// 就诊ID // 就诊ID
Long encounterId = saveDiagnosisParam.getEncounterId(); Long encounterId = saveDiagnosisParam.getEncounterId();
// 诊断定义集合 // 诊断定义集合
List<SaveDiagnosisChildParam> diagnosisChildList = saveDiagnosisParam.getDiagnosisChildList(); List<SaveDiagnosisChildParam> diagnosisChildList = saveDiagnosisParam.getDiagnosisChildList();
// 校验患者ID和就诊ID
if (patientId == null || encounterId == null) {
return R.fail(MessageUtils.message(PromptMsgConstant.Common.M00009, new Object[] { "患者ID或就诊ID" }));
}
// 校验诊断列表不为空
if (diagnosisChildList == null || diagnosisChildList.isEmpty()) {
return R.fail(MessageUtils.message(PromptMsgConstant.Common.M00009, new Object[] { "诊断列表" }));
}
// 先删除再保存 // 先删除再保存
// iEncounterDiagnosisService.deleteEncounterDiagnosisInfos(encounterId); // iEncounterDiagnosisService.deleteEncounterDiagnosisInfos(encounterId);

View File

@@ -94,8 +94,39 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
* 保存检验申请单信息逻辑 * 保存检验申请单信息逻辑
* 保存检验申请单信息同时根据检验申请单检验项目数据保存检验申请单明细信息 * 保存检验申请单信息同时根据检验申请单检验项目数据保存检验申请单明细信息
*/ */
log.debug("保存检验申请单信息:{}", doctorStationLabApplyDto);
log.debug("保存申请单明细信息:{}",doctorStationLabApplyDto.getLabApplyItemList()); // 申请单号为空或"待生成"时,由后端生成新单号
String applyNo = doctorStationLabApplyDto.getApplyNo();
boolean isNewApplyNo = false;
if (applyNo == null || applyNo.trim().isEmpty() || "待生成".equals(applyNo) || "自动生成".equals(applyNo)) {
applyNo = generateApplyNo();
isNewApplyNo = true;
}
// 将生成的单号设置回 DTO
doctorStationLabApplyDto.setApplyNo(applyNo);
try {
// 执行保存逻辑
doSaveInspectionLabApply(doctorStationLabApplyDto, applyNo);
} catch (Exception e) {
// 记录废号日志(申请单号已生成但保存失败)
if (isNewApplyNo) {
log.error("申请单号 {} 因保存失败成为废号,原因:{}", applyNo, e.getMessage());
}
throw e; // 重新抛出异常,让事务回滚
}
// 返回生成的申请单号
Map<String, Object> result = new HashMap<>();
result.put("applyNo", applyNo);
return R.ok(result);
}
/**
* 执行保存检验申请单的实际逻辑
*/
private void doSaveInspectionLabApply(DoctorStationLabApplyDto doctorStationLabApplyDto, String applyNo) {
//获取当前登陆用户 ID //获取当前登陆用户 ID
String userId = String.valueOf(SecurityUtils.getLoginUser().getUserId()); String userId = String.valueOf(SecurityUtils.getLoginUser().getUserId());
InspectionLabApply inspectionLabApply = new InspectionLabApply(); InspectionLabApply inspectionLabApply = new InspectionLabApply();
@@ -108,18 +139,30 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
inspectionLabApply.setOperatorId(userId); inspectionLabApply.setOperatorId(userId);
inspectionLabApply.setCreateTime(new Date()); inspectionLabApply.setCreateTime(new Date());
inspectionLabApply.setDeleteFlag(DelFlag.NO.getCode()); inspectionLabApply.setDeleteFlag(DelFlag.NO.getCode());
// 申请日期使用服务器当前系统时间
inspectionLabApply.setApplyTime(new Date());
log.debug("保存检验申请单信息:{}", inspectionLabApply);
inspectionLabApplyService.saveOrUpdate(inspectionLabApply); inspectionLabApplyService.saveOrUpdate(inspectionLabApply);
// 金额校验和重算:后端重新计算金额,防止前端篡改
java.math.BigDecimal totalAmount = java.math.BigDecimal.ZERO;
int index = 0;
//遍历 doctorStationLabApplyDto.getLabApplyItemList() //遍历 doctorStationLabApplyDto.getLabApplyItemList()
int index = 0;
for (DoctorStationLabApplyItemDto doctorStationLabApplyItemDto : doctorStationLabApplyDto.getLabApplyItemList()) { for (DoctorStationLabApplyItemDto doctorStationLabApplyItemDto : doctorStationLabApplyDto.getLabApplyItemList()) {
//将 dto 数据复制到 InspectionLabApplyItem 对象中 //将 dto 数据复制到 InspectionLabApplyItem 对象中
InspectionLabApplyItem inspectionLabApplyItem = new InspectionLabApplyItem(); InspectionLabApplyItem inspectionLabApplyItem = new InspectionLabApplyItem();
BeanUtils.copyProperties(doctorStationLabApplyItemDto, inspectionLabApplyItem); BeanUtils.copyProperties(doctorStationLabApplyItemDto, inspectionLabApplyItem);
// 后端重新计算金额:金额 = 单价 × 数量
java.math.BigDecimal itemPrice = doctorStationLabApplyItemDto.getItemPrice();
java.math.BigDecimal itemQty = doctorStationLabApplyItemDto.getItemQty();
if (itemPrice != null && itemQty != null) {
java.math.BigDecimal calculatedAmount = itemPrice.multiply(itemQty).setScale(2, java.math.RoundingMode.HALF_UP);
inspectionLabApplyItem.setItemAmount(calculatedAmount);
totalAmount = totalAmount.add(calculatedAmount);
}
//设置从表申请单明细的申请单号 //设置从表申请单明细的申请单号
inspectionLabApplyItem.setApplyNo(doctorStationLabApplyDto.getApplyNo()); inspectionLabApplyItem.setApplyNo(doctorStationLabApplyDto.getApplyNo());
//执行科室代码,取值于检验申请单明细(前端传递的字典值) //执行科室代码,取值于检验申请单明细(前端传递的字典值)
@@ -131,7 +174,6 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
index++; index++;
inspectionLabApplyItem.setDeleteFlag(DelFlag.NO.getCode()); inspectionLabApplyItem.setDeleteFlag(DelFlag.NO.getCode());
log.debug("保存申请单明细信息:{}", inspectionLabApplyItem);
inspectionLabApplyItemService.saveOrUpdate(inspectionLabApplyItem); inspectionLabApplyItemService.saveOrUpdate(inspectionLabApplyItem);
//创建条码对象 //创建条码对象
@@ -147,8 +189,6 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
barCode.setCreateTime(new Date()); barCode.setCreateTime(new Date());
barCode.setDeleteFlag(DelFlag.NO.getCode()); barCode.setDeleteFlag(DelFlag.NO.getCode());
log.debug("插入条码数据前barCode:{}",barCode);
inspectionLabBarCodeService.saveOrUpdate(barCode); inspectionLabBarCodeService.saveOrUpdate(barCode);
} }
@@ -195,15 +235,12 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
); );
if (organization != null) { if (organization != null) {
positionId = organization.getId(); positionId = organization.getId();
} else {
log.warn("未找到执行科室代码对应的科室:{}", performDeptCode);
} }
} }
// 如果没有指定执行科室,使用当前医生所在的科室作为默认执行科室 // 如果没有指定执行科室,使用当前医生所在的科室作为默认执行科室
if (positionId == null) { if (positionId == null) {
positionId = SecurityUtils.getDeptId(); positionId = SecurityUtils.getDeptId();
log.debug("检验项目未指定执行科室,使用当前科室:{}", positionId);
} }
// 4. 创建医嘱保存对象 // 4. 创建医嘱保存对象
@@ -282,12 +319,7 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
adviceSaveParam.setAdviceSaveList(adviceSaveList); adviceSaveParam.setAdviceSaveList(adviceSaveList);
// 调用门诊医嘱保存接口,创建关联的医嘱记录 // 调用门诊医嘱保存接口,创建关联的医嘱记录
try {
iDoctorStationAdviceAppService.saveAdvice(adviceSaveParam, "1"); // "1"表示保存操作 iDoctorStationAdviceAppService.saveAdvice(adviceSaveParam, "1"); // "1"表示保存操作
} catch (Exception e) {
throw new RuntimeException("创建关联医嘱记录失败", e);
}
return R.ok();
} }
/** /**
@@ -342,11 +374,11 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
// 查询检验申请单列表 // 查询检验申请单列表
log.debug("查询申请单数据前"); log.debug("查询申请单数据前");
List<InspectionLabApply> list = doctorStationLabApplyMapper.getInspectionApplyListPage(encounterId); List<DoctorStationLabApplyDto> list = doctorStationLabApplyMapper.getInspectionApplyListPage(encounterId);
log.debug("查询申请单数据后"); log.debug("查询申请单数据后");
// 使用 PageInfo 包装查询结果 // 使用 PageInfo 包装查询结果
PageInfo<InspectionLabApply> pageInfo = new PageInfo<>(list); PageInfo<DoctorStationLabApplyDto> pageInfo = new PageInfo<>(list);
// 构建返回结果 // 构建返回结果
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
@@ -559,8 +591,7 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
* 支持并发安全:使用 Redis 原子递增保证唯一性 * 支持并发安全:使用 Redis 原子递增保证唯一性
* @return 申请单号 * @return 申请单号
*/ */
@Override private String generateApplyNo() {
public String generateApplyNo() {
// 获取当前日期 // 获取当前日期
LocalDate today = LocalDate.now(); LocalDate today = LocalDate.now();
String dateStr = today.format(DateTimeFormatter.ofPattern("yyyyMMdd")); String dateStr = today.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
@@ -574,8 +605,8 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
// 使用 Redis 原子递增获取流水号(并发安全) // 使用 Redis 原子递增获取流水号(并发安全)
long sequence = redisCache.incr(redisKey, 1); long sequence = redisCache.incr(redisKey, 1);
// 设置 Redis key 过期时间为 2 天,避免数据积累 // 设置 Redis key 过期时间(每天的 key 按日期独立隔天不再使用25小时确保跨午夜场景安全
redisCache.expire(redisKey, 2 * 24 * 60 * 60); redisCache.expire(redisKey, 25 * 60 * 60);
// 格式化流水号为5位不足前补0 // 格式化流水号为5位不足前补0
String sequenceStr = String.format("%05d", sequence); String sequenceStr = String.format("%05d", sequence);
@@ -583,7 +614,6 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
// 生成完整的申请单号 // 生成完整的申请单号
String applyNo = prefix + sequenceStr; String applyNo = prefix + sequenceStr;
log.debug("生成检验申请单号:{}", applyNo);
return applyNo; return applyNo;
} }

View File

@@ -1,6 +1,7 @@
package com.openhis.web.doctorstation.appservice.impl; package com.openhis.web.doctorstation.appservice.impl;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
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.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -16,21 +17,23 @@ import com.openhis.common.constant.CommonConstants;
import com.openhis.common.enums.*; import com.openhis.common.enums.*;
import com.openhis.common.utils.EnumUtils; import com.openhis.common.utils.EnumUtils;
import com.openhis.common.utils.HisQueryUtils; import com.openhis.common.utils.HisQueryUtils;
import com.openhis.triageandqueuemanage.domain.TriageQueueItem;
import com.openhis.triageandqueuemanage.service.TriageQueueItemService;
import com.openhis.web.doctorstation.appservice.*; import com.openhis.web.doctorstation.appservice.*;
import com.openhis.web.doctorstation.dto.PatientInfoDto; import com.openhis.web.doctorstation.dto.PatientInfoDto;
import com.openhis.web.doctorstation.dto.PrescriptionInfoBaseDto; import com.openhis.web.doctorstation.dto.PrescriptionInfoBaseDto;
import com.openhis.web.doctorstation.dto.PrescriptionInfoDetailDto; import com.openhis.web.doctorstation.dto.PrescriptionInfoDetailDto;
import com.openhis.web.doctorstation.dto.ReceptionStatisticsDto; import com.openhis.web.doctorstation.dto.ReceptionStatisticsDto;
import com.openhis.web.doctorstation.mapper.DoctorStationMainAppMapper; import com.openhis.web.doctorstation.mapper.DoctorStationMainAppMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -64,6 +67,9 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
@Resource @Resource
private JdbcTemplate jdbcTemplate; private JdbcTemplate jdbcTemplate;
@Resource
private TriageQueueItemService triageQueueItemService;
/** /**
* 查询就诊患者信息 * 查询就诊患者信息
* *
@@ -124,14 +130,40 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
* @return 结果 * @return 结果
*/ */
@Override @Override
@Transactional(rollbackFor = Exception.class)
public R<?> receiveEncounter(Long encounterId) { public R<?> receiveEncounter(Long encounterId) {
Integer tenantId = SecurityUtils.getLoginUser().getTenantId(); Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
String currentUsername = SecurityUtils.getUsername(); String currentUsername = SecurityUtils.getUsername();
// 检查就诊记录是否存在
Encounter encounter = encounterMapper.selectById(encounterId);
if (encounter == null) {
return R.fail("就诊记录不存在");
}
// 检查患者状态,防止重复接诊
Integer currentStatus = encounter.getStatusEnum();
if (EncounterStatus.IN_PROGRESS.getValue().equals(currentStatus)) {
return R.fail("已接诊,请勿重复点击,已为您刷新");
}
int update = encounterMapper.update(null, int update = encounterMapper.update(null,
new LambdaUpdateWrapper<Encounter>().eq(Encounter::getId, encounterId) new LambdaUpdateWrapper<Encounter>().eq(Encounter::getId, encounterId)
.eq(Encounter::getStatusEnum, EncounterStatus.PLANNED.getValue()) // 只更新待诊状态的患者
.set(Encounter::getReceptionTime, new Date()) .set(Encounter::getReceptionTime, new Date())
.set(Encounter::getStatusEnum, EncounterStatus.IN_PROGRESS.getValue()) .set(Encounter::getStatusEnum, EncounterStatus.IN_PROGRESS.getValue())
.set(Encounter::getSubjectStatusEnum, EncounterSubjectStatus.RECEIVING_CARE.getValue())); .set(Encounter::getSubjectStatusEnum, EncounterSubjectStatus.RECEIVING_CARE.getValue()));
// 如果更新失败,说明状态已被其他医生修改
if (update <= 0) {
// 重新查询当前状态
encounter = encounterMapper.selectById(encounterId);
if (EncounterStatus.IN_PROGRESS.getValue().equals(encounter.getStatusEnum())) {
return R.fail("已接诊,请勿重复接诊");
}
return R.fail("接诊失败,请刷新后重试");
}
// 先把之前的接诊记录更新为已完成 // 先把之前的接诊记录更新为已完成
iEncounterParticipantService.update(new LambdaUpdateWrapper<EncounterParticipant>() iEncounterParticipantService.update(new LambdaUpdateWrapper<EncounterParticipant>()
.eq(EncounterParticipant::getTypeCode, ParticipantType.ADMITTER.getCode()) .eq(EncounterParticipant::getTypeCode, ParticipantType.ADMITTER.getCode())
@@ -148,7 +180,28 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
encounterParticipant.setCreateBy(currentUsername); encounterParticipant.setCreateBy(currentUsername);
encounterParticipant.setCreateTime(new Date()); encounterParticipant.setCreateTime(new Date());
iEncounterParticipantService.save(encounterParticipant); iEncounterParticipantService.save(encounterParticipant);
return update > 0 ? R.ok() : R.fail();
// 更新 triage_queue_item 队列记录状态为 CALLING
try {
TriageQueueItem queueItem = triageQueueItemService.getOne(
new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getEncounterId, encounterId)
.eq(TriageQueueItem::getDeleteFlag, "0")
);
if (queueItem != null) {
queueItem.setStatus("CALLING");
queueItem.setUpdateTime(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS));
triageQueueItemService.updateById(queueItem);
log.info("接诊时更新队列状态为CALLINGencounterId={}, queueItemId={}", encounterId, queueItem.getId());
} else {
log.warn("接诊时未找到队列记录encounterId={}", encounterId);
}
} catch (Exception e) {
log.error("接诊时更新队列状态失败encounterId={}", encounterId, e);
}
return R.ok();
} }
/** /**
@@ -181,11 +234,54 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
return R.fail("就诊记录不存在"); return R.fail("就诊记录不存在");
} }
if (!EncounterStatus.IN_PROGRESS.getValue().equals(encounter.getStatusEnum())) { // 检查患者状态,防止重复完诊
return R.fail("当前患者不在就诊中状态"); Integer currentStatus = encounter.getStatusEnum();
if (EncounterStatus.DISCHARGED.getValue().equals(currentStatus) ||
EncounterStatus.COMPLETED.getValue().equals(currentStatus)) {
// 患者已完成就诊,返回特定提示
return R.fail("患者已完成就诊,已为您自动刷新患者列表");
} }
// 2. 更新状态、完成时间以及初复诊标识 if (!EncounterStatus.IN_PROGRESS.getValue().equals(currentStatus)) {
return R.fail("非就诊中患者不能完诊");
}
// 2. 查找队列项
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
TriageQueueItem queueItem = triageQueueItemService.getOne(
new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getEncounterId, encounterId)
.eq(TriageQueueItem::getDeleteFlag, "0")
);
// 如果队列项存在,检查状态并更新
if (queueItem != null && "CALLING".equals(queueItem.getStatus())) {
// 更新队列状态为已完成
java.time.LocalDateTime nowLocal = java.time.LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS);
queueItem.setStatus("COMPLETED");
queueItem.setUpdateTime(nowLocal);
triageQueueItemService.updateById(queueItem);
// 写入 div_log 审计日志
try {
Long userId = SecurityUtils.getLoginUser().getUserId();
String divLogSql = "INSERT INTO hisdev.div_log "
+ "(pool_id, slot_id, queue_no, op_user_id, action, create_time) "
+ "VALUES (?, ?, ?, ?, 'COMPLETE', NOW()::timestamp(0))";
jdbcTemplate.update(divLogSql,
queueItem.getOrganizationId(), // pool_id: 候选池ID科室
queueItem.getPractitionerId(), // slot_id: 槽位ID医生
queueItem.getQueueOrder(), // queue_no: 队列号
userId); // op_user_id: 操作用户ID
} catch (Exception e) {
log.error("写入div_log审计日志失败", e);
// 审计日志失败不影响主流程
}
}
// 3. 更新状态、完成时间以及初复诊标识
Date now = new Date(); Date now = new Date();
int update = encounterMapper.update(null, int update = encounterMapper.update(null,
new LambdaUpdateWrapper<Encounter>() new LambdaUpdateWrapper<Encounter>()
@@ -198,7 +294,7 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
if (update <= 0) return R.fail("完诊失败"); if (update <= 0) return R.fail("完诊失败");
// 3. 审计日志 // 4. 审计日志sys_oper_log
try { try {
String username = SecurityUtils.getUsernameSafe(); String username = SecurityUtils.getUsernameSafe();
String sql = "INSERT INTO sys_oper_log " String sql = "INSERT INTO sys_oper_log "

View File

@@ -52,7 +52,7 @@ public class DoctorStationAdviceController {
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo, @RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) { @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
return R.ok(iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId, return R.ok(iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId,
adviceDefinitionIdParamList, organizationId, pageNo, pageSize, Whether.NO.getValue(), adviceTypes, null)); adviceDefinitionIdParamList, organizationId, pageNo, pageSize, Whether.NO.getValue(), adviceTypes, null, null));
} }
/** /**

View File

@@ -64,18 +64,4 @@ public class DoctorStationInspectionLabApplyController {
log.debug("删除检验申请单:{}", applyNo); log.debug("删除检验申请单:{}", applyNo);
return R.ok(iDoctorStationInspectionLabApplyService.deleteInspectionLabApply(applyNo)); return R.ok(iDoctorStationInspectionLabApplyService.deleteInspectionLabApply(applyNo));
} }
/**
* 生成检验申请单号
* 规则LS + YYYYMMDD + 5位流水号每日从1开始递增
* @return 申请单号
*/
@GetMapping(value = "/generate-apply-no")
public R<?> generateApplyNo(){
log.debug("生成检验申请单号");
String applyNo = iDoctorStationInspectionLabApplyService.generateApplyNo();
Map<String, String> result = new HashMap<>();
result.put("applyNo", applyNo);
return R.ok(result);
}
} }

View File

@@ -17,6 +17,10 @@ import java.util.List;
@Data @Data
@Accessors(chain = true) @Accessors(chain = true)
public class DoctorStationLabApplyDto { public class DoctorStationLabApplyDto {
/**
* 申请单ID数据库自增主键
*/
private Long applicationId;
/** /**
* 申请单编号 * 申请单编号
*/ */

View File

@@ -161,6 +161,11 @@ public class RequestBaseDto {
private String doseUnitCode; private String doseUnitCode;
private String doseUnitCode_dictText; private String doseUnitCode_dictText;
/**
* 单价
*/
private BigDecimal unitPrice;
/** /**
* 总价 * 总价
*/ */
@@ -215,4 +220,16 @@ public class RequestBaseDto {
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private Long basedOnId; private Long basedOnId;
/**
* 就诊id
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long encounterId;
/**
* 患者id
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long patientId;
} }

View File

@@ -2,6 +2,7 @@ package com.openhis.web.doctorstation.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
@@ -73,12 +74,16 @@ public class SaveDiagnosisChildParam {
/** /**
* 诊断时间 * 诊断时间
* 添加 pattern 以支持前端传来的 "yyyy/M/d HH:mm:ss" 格式
*/ */
@JsonFormat(pattern = "yyyy/M/d HH:mm:ss", timezone = "GMT+8")
private Date diagnosisTime; private Date diagnosisTime;
/** /**
* 发病时间 * 发病时间
* 同样添加 pattern 以防前端传来相同格式的发病时间
*/ */
@JsonFormat(pattern = "yyyy/M/d HH:mm:ss", timezone = "GMT+8")
private Date onsetDate; private Date onsetDate;
/** 患者疾病诊断类型代码 */ /** 患者疾病诊断类型代码 */

View File

@@ -38,6 +38,7 @@ public interface DoctorStationAdviceAppMapper {
@Param("adviceDefinitionIdParamList") List<Long> adviceDefinitionIdParamList, @Param("adviceDefinitionIdParamList") List<Long> adviceDefinitionIdParamList,
@Param("adviceTypes") List<Integer> adviceTypes, @Param("adviceTypes") List<Integer> adviceTypes,
@Param("searchKey") String searchKey, @Param("searchKey") String searchKey,
@Param("categoryCode") String categoryCode,
@Param(Constants.WRAPPER) QueryWrapper<AdviceBaseDto> queryWrapper); @Param(Constants.WRAPPER) QueryWrapper<AdviceBaseDto> queryWrapper);
/** /**

View File

@@ -1,6 +1,6 @@
package com.openhis.web.doctorstation.mapper; package com.openhis.web.doctorstation.mapper;
import com.openhis.lab.domain.InspectionLabApply; import com.openhis.web.doctorstation.dto.DoctorStationLabApplyDto;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@@ -23,5 +23,5 @@ public interface DoctorStationLabApplyMapper {
* @param encounterId 就诊 ID * @param encounterId 就诊 ID
* @return 检验申请单列表 * @return 检验申请单列表
*/ */
List<InspectionLabApply> getInspectionApplyListPage(@Param("encounterId") Long encounterId); List<DoctorStationLabApplyDto> getInspectionApplyListPage(@Param("encounterId") Long encounterId);
} }

View File

@@ -115,6 +115,15 @@ public class AdviceUtils {
matched = true; matched = true;
// 检查库存是否充足 // 检查库存是否充足
BigDecimal minUnitQuantity = saveDto.getMinUnitQuantity(); BigDecimal minUnitQuantity = saveDto.getMinUnitQuantity();
// 🔧 Bug Fix: 对于耗材类型如果没有设置minUnitQuantity则使用quantity作为默认值
if (minUnitQuantity == null) {
if (CommonConstants.TableName.ADM_DEVICE_DEFINITION.equals(inventoryDto.getItemTable())) {
// 耗材只有一个单位minUnitQuantity等于quantity
minUnitQuantity = saveDto.getQuantity();
} else {
return saveDto.getAdviceName() + "的小单位数量不能为空";
}
}
BigDecimal chineseHerbsDoseQuantity = saveDto.getChineseHerbsDoseQuantity(); // 中药付数 BigDecimal chineseHerbsDoseQuantity = saveDto.getChineseHerbsDoseQuantity(); // 中药付数
// 中草药医嘱的情况 // 中草药医嘱的情况
if (chineseHerbsDoseQuantity != null && chineseHerbsDoseQuantity.compareTo(BigDecimal.ZERO) > 0) { if (chineseHerbsDoseQuantity != null && chineseHerbsDoseQuantity.compareTo(BigDecimal.ZERO) > 0) {
@@ -364,7 +373,7 @@ public class AdviceUtils {
// 对应的子项诊疗医嘱信息 // 对应的子项诊疗医嘱信息
AdviceBaseDto activityAdviceBaseDto AdviceBaseDto activityAdviceBaseDto
= iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null, null, null, organizationId, 1, = iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null, null, null, organizationId, 1,
1, Whether.NO.getValue(), List.of(1, 2, 3), null).getRecords().get(0); 1, Whether.NO.getValue(), List.of(1, 2, 3), null, null).getRecords().get(0);
if (activityAdviceBaseDto != null) { if (activityAdviceBaseDto != null) {
// 费用定价 // 费用定价
AdvicePriceDto advicePriceDto = activityAdviceBaseDto.getPriceList().get(0); AdvicePriceDto advicePriceDto = activityAdviceBaseDto.getPriceList().get(0);

View File

@@ -32,9 +32,11 @@ public class PrescriptionUtils {
if (medicineList == null || medicineList.isEmpty()) { if (medicineList == null || medicineList.isEmpty()) {
return; return;
} }
// 1. 按诊断ID分组不同诊断必须分开 // 1. 按诊断ID分组不同诊断必须分开null值归为一组
Map<Long, List<AdviceSaveDto>> diagnosisGroups = Map<Long, List<AdviceSaveDto>> diagnosisGroups =
medicineList.stream().collect(Collectors.groupingBy(AdviceSaveDto::getConditionDefinitionId)); medicineList.stream().collect(Collectors.groupingBy(dto ->
dto.getConditionDefinitionId() != null ? dto.getConditionDefinitionId() : 0L
));
// 2. 处理每个诊断组 // 2. 处理每个诊断组
diagnosisGroups.values().forEach(this::processDiagnosisGroup); diagnosisGroups.values().forEach(this::processDiagnosisGroup);
} }
@@ -46,9 +48,11 @@ public class PrescriptionUtils {
if (diagnosisGroup.isEmpty()) { if (diagnosisGroup.isEmpty()) {
return; return;
} }
// 1. 按药品性质分组 // 1. 按药品性质分组null值归为普通药品
Map<String, List<AdviceSaveDto>> pharmacologyGroups = Map<String, List<AdviceSaveDto>> pharmacologyGroups =
diagnosisGroup.stream().collect(Collectors.groupingBy(AdviceSaveDto::getPharmacologyCategoryCode)); diagnosisGroup.stream().collect(Collectors.groupingBy(dto ->
dto.getPharmacologyCategoryCode() != null ? dto.getPharmacologyCategoryCode() : "0"
));
// 2. 处理每个药品性质组 // 2. 处理每个药品性质组
pharmacologyGroups.values().forEach(pharmaGroup -> { pharmacologyGroups.values().forEach(pharmaGroup -> {
// 2.1 先处理有分组ID的药品确保它们不会被拆分 // 2.1 先处理有分组ID的药品确保它们不会被拆分

View File

@@ -702,7 +702,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
= medUseExeList.stream().map(MedicationRequestUseExe::getMedicationId).collect(Collectors.toList()); = medUseExeList.stream().map(MedicationRequestUseExe::getMedicationId).collect(Collectors.toList());
// 医嘱详细信息 // 医嘱详细信息
List<AdviceBaseDto> medicationInfos = doctorStationAdviceAppService.getAdviceBaseInfo(null, null, null, List<AdviceBaseDto> medicationInfos = doctorStationAdviceAppService.getAdviceBaseInfo(null, null, null,
medicationDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(1), null).getRecords(); medicationDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(1), null, null).getRecords();
// 当前时间 // 当前时间
Date curDate = new Date(); Date curDate = new Date();
@@ -979,7 +979,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
= actUseExeList.stream().map(ServiceRequestUseExe::getActivityId).collect(Collectors.toList()); = actUseExeList.stream().map(ServiceRequestUseExe::getActivityId).collect(Collectors.toList());
// 医嘱详细信息 // 医嘱详细信息
List<AdviceBaseDto> activityInfos = doctorStationAdviceAppService.getAdviceBaseInfo(null, null, null, List<AdviceBaseDto> activityInfos = doctorStationAdviceAppService.getAdviceBaseInfo(null, null, null,
activityDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(3), null).getRecords(); activityDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(3), null, null).getRecords();
// 当前时间 // 当前时间
Date curDate = new Date(); Date curDate = new Date();
@@ -1146,7 +1146,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
// 耗材医嘱详细信息 // 耗材医嘱详细信息
List<AdviceBaseDto> deviceInfos = doctorStationAdviceAppService List<AdviceBaseDto> deviceInfos = doctorStationAdviceAppService
.getAdviceBaseInfo(null, null, null, deviceIds, 0L, 1, 500, Whether.NO.getValue(), List.of(2), null) .getAdviceBaseInfo(null, null, null, deviceIds, 0L, 1, 500, Whether.NO.getValue(), List.of(2), null, null)
.getRecords(); .getRecords();
DeviceRequest deviceRequest; DeviceRequest deviceRequest;

View File

@@ -201,7 +201,7 @@ public class EncounterAutoRollAppServiceImpl implements IEncounterAutoRollAppSer
.map(AutoRollNursingDto::getActivityDefinitionId).collect(Collectors.toList()); .map(AutoRollNursingDto::getActivityDefinitionId).collect(Collectors.toList());
// 诊疗医嘱信息 // 诊疗医嘱信息
List<AdviceBaseDto> activityInfos = doctorStationAdviceAppService.getAdviceBaseInfo(null, null, null, List<AdviceBaseDto> activityInfos = doctorStationAdviceAppService.getAdviceBaseInfo(null, null, null,
activityDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(3), orderPricing).getRecords(); activityDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(3), orderPricing, null).getRecords();
// 计费 // 计费
ChargeItem chargeItem; ChargeItem chargeItem;
@@ -295,7 +295,7 @@ public class EncounterAutoRollAppServiceImpl implements IEncounterAutoRollAppSer
.map(AutoRollBasicServiceDto::getActivityDefinitionId).collect(Collectors.toList()); .map(AutoRollBasicServiceDto::getActivityDefinitionId).collect(Collectors.toList());
// 诊疗医嘱信息 // 诊疗医嘱信息
List<AdviceBaseDto> activityInfos = doctorStationAdviceAppService.getAdviceBaseInfo(null, null, null, List<AdviceBaseDto> activityInfos = doctorStationAdviceAppService.getAdviceBaseInfo(null, null, null,
activityDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(3), orderPricing).getRecords(); activityDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(3), orderPricing, null).getRecords();
// 计费 // 计费
ChargeItem chargeItem; ChargeItem chargeItem;
for (AutoRollBasicServiceDto autoRollBasicServiceDto : autoRollBasicService) { for (AutoRollBasicServiceDto autoRollBasicServiceDto : autoRollBasicService) {

View File

@@ -0,0 +1,45 @@
package com.openhis.web.lab.appservice;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentSelParam;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentUpDto;
import com.core.common.core.domain.R;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* 检验项目 AppService 接口(独立操作 lab_activity_definition 表)
*/
public interface ILabActivityDefinitionAppService {
/**
* 分页查询检验项目列表
*/
R<?> getLabActivityDefinitionPage(DiagnosisTreatmentSelParam selParam, String searchKey,
Integer pageNo, Integer pageSize, HttpServletRequest request);
/**
* 根据id查询检验项目详情
*/
R<?> getLabActivityDefinitionOne(Long id);
/**
* 新增检验项目
*/
R<?> addLabActivityDefinition(DiagnosisTreatmentUpDto dto);
/**
* 编辑检验项目
*/
R<?> editLabActivityDefinition(DiagnosisTreatmentUpDto dto);
/**
* 停用检验项目
*/
R<?> stopLabActivityDefinition(List<Long> ids);
/**
* 启用检验项目
*/
R<?> startLabActivityDefinition(List<Long> ids);
}

View File

@@ -0,0 +1,189 @@
package com.openhis.web.lab.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.core.common.core.domain.model.LoginUser;
import com.core.common.utils.*;
import com.core.common.utils.bean.BeanUtils;
import com.openhis.common.constant.PromptMsgConstant;
import com.openhis.common.enums.ActivityType;
import com.openhis.common.enums.PublicationStatus;
import com.openhis.common.enums.Whether;
import com.core.common.utils.ChineseConvertUtils;
import com.openhis.common.utils.EnumUtils;
import com.openhis.common.utils.HisQueryUtils;
import com.openhis.common.enums.AssignSeqEnum;
import com.openhis.lab.domain.LabActivityDefinition;
import com.openhis.lab.service.ILabActivityDefinitionService;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentSelParam;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentUpDto;
import com.openhis.web.lab.appservice.ILabActivityDefinitionAppService;
import com.openhis.web.datadictionary.mapper.LabActivityDefinitionManageMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 检验项目 AppService 实现(独立操作 lab_activity_definition 表)
*/
@Service
public class LabActivityDefinitionAppServiceImpl implements ILabActivityDefinitionAppService {
@Resource
private ILabActivityDefinitionService labActivityDefinitionService;
@Resource
private LabActivityDefinitionManageMapper labActivityDefinitionManageMapper;
@Resource
private AssignSeqUtil assignSeqUtil;
@Override
public R<?> getLabActivityDefinitionPage(DiagnosisTreatmentSelParam selParam, String searchKey,
Integer pageNo, Integer pageSize, HttpServletRequest request) {
if (selParam == null) {
selParam = new DiagnosisTreatmentSelParam();
}
if (selParam.getStatusEnum() == null) {
selParam.setStatusEnum(PublicationStatus.ACTIVE.getValue());
}
// 临时移除需要手动拼别名条件的字段
Long inspectionTypeIdValue = null;
if (selParam.getInspectionTypeId() != null) {
inspectionTypeIdValue = selParam.getInspectionTypeId();
selParam.setInspectionTypeId(null);
}
Integer pricingFlagValue = null;
if (selParam.getPricingFlag() != null) {
pricingFlagValue = selParam.getPricingFlag();
selParam.setPricingFlag(null);
}
QueryWrapper<DiagnosisTreatmentDto> queryWrapper = HisQueryUtils.buildQueryWrapper(selParam,
searchKey, new HashSet<>(Arrays.asList("T1.bus_no", "T1.name", "T1.py_str", "T1.wb_str")), request);
if (inspectionTypeIdValue != null) {
queryWrapper.eq("T1.inspection_type_id", inspectionTypeIdValue);
selParam.setInspectionTypeId(inspectionTypeIdValue);
}
if (pricingFlagValue != null) {
queryWrapper.eq("T1.pricing_flag", pricingFlagValue);
selParam.setPricingFlag(pricingFlagValue);
}
IPage<DiagnosisTreatmentDto> page = labActivityDefinitionManageMapper
.getLabActivityDefinitionPage(new Page<>(pageNo, pageSize), queryWrapper);
page.getRecords().forEach(e -> {
e.setYbFlag_enumText(EnumUtils.getInfoByValue(Whether.class, e.getYbFlag()));
e.setYbMatchFlag_enumText(EnumUtils.getInfoByValue(Whether.class, e.getYbMatchFlag()));
e.setTypeEnum_enumText(EnumUtils.getInfoByValue(ActivityType.class, e.getTypeEnum()));
e.setStatusEnum_enumText(EnumUtils.getInfoByValue(PublicationStatus.class, e.getStatusEnum()));
e.setPricingFlag_enumText(EnumUtils.getInfoByValue(Whether.class, e.getPricingFlag()));
});
return R.ok(page);
}
@Override
public R<?> getLabActivityDefinitionOne(Long id) {
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
DiagnosisTreatmentDto dto = labActivityDefinitionManageMapper.getLabActivityDefinitionOne(id, tenantId);
return R.ok(dto);
}
@Override
public R<?> addLabActivityDefinition(DiagnosisTreatmentUpDto dto) {
if (dto.getOrgId() == null) {
dto.setOrgId(SecurityUtils.getLoginUser().getHospitalId());
}
LabActivityDefinition lab = new LabActivityDefinition();
BeanUtils.copyProperties(dto, lab);
lab.setSortOrder(dto.getSortOrder())
.setServiceRange(dto.getServiceRange())
.setInspectionTypeId(dto.getInspectionTypeId())
.setFeePackageId(dto.getFeePackageId())
.setSubItemId(dto.getSubItemId());
if (StringUtils.isEmpty(lab.getBusNo())) {
lab.setBusNo(assignSeqUtil.getSeq(AssignSeqEnum.ACTIVITY_DEFINITION_NUM.getPrefix(), 10));
}
lab.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(lab.getName()));
lab.setWbStr(ChineseConvertUtils.toWBFirstLetter(lab.getName()));
lab.setStatusEnum(PublicationStatus.ACTIVE.getValue());
// 设置创建者和租户ID
String createBy = "system";
Integer tenantId = null;
try {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser != null) {
createBy = loginUser.getUsername();
tenantId = loginUser.getTenantId();
}
} catch (Exception e) {
// 使用默认值
}
lab.setCreateBy(createBy);
lab.setTenantId(tenantId != null ? tenantId : 1);
if (lab.getCreateTime() == null) {
lab.setCreateTime(new java.util.Date());
}
return labActivityDefinitionService.addLabActivityDefinition(lab)
? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"检验项目"}))
: R.fail(null, "检验编码已存在:" + lab.getBusNo());
}
@Override
public R<?> editLabActivityDefinition(DiagnosisTreatmentUpDto dto) {
LabActivityDefinition lab = new LabActivityDefinition();
BeanUtils.copyProperties(dto, lab);
lab.setSortOrder(dto.getSortOrder())
.setServiceRange(dto.getServiceRange())
.setInspectionTypeId(dto.getInspectionTypeId())
.setFeePackageId(dto.getFeePackageId())
.setSubItemId(dto.getSubItemId())
.setPricingFlag(dto.getPricingFlag());
lab.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(lab.getName()));
lab.setWbStr(ChineseConvertUtils.toWBFirstLetter(lab.getName()));
return labActivityDefinitionService.updateById(lab)
? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"检验项目"}))
: R.fail(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00007, null));
}
@Override
public R<?> stopLabActivityDefinition(List<Long> ids) {
List<LabActivityDefinition> labList = new CopyOnWriteArrayList<>();
for (Long id : ids) {
LabActivityDefinition lab = new LabActivityDefinition();
lab.setId(id).setStatusEnum(PublicationStatus.RETIRED.getValue());
labList.add(lab);
}
labActivityDefinitionService.updateBatchById(labList);
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"检验项目"}));
}
@Override
public R<?> startLabActivityDefinition(List<Long> ids) {
List<LabActivityDefinition> labList = new CopyOnWriteArrayList<>();
for (Long id : ids) {
LabActivityDefinition lab = new LabActivityDefinition();
lab.setId(id).setStatusEnum(PublicationStatus.ACTIVE.getValue());
labList.add(lab);
}
labActivityDefinitionService.updateBatchById(labList);
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"检验项目"}));
}
}

View File

@@ -94,7 +94,6 @@ public class InspectionTypeController extends BaseController {
// 查询是否存在相同编码的记录 // 查询是否存在相同编码的记录
List<InspectionType> existingRecords = inspectionTypeService.list(queryWrapper); List<InspectionType> existingRecords = inspectionTypeService.list(queryWrapper);
log.debug("检查编码唯一性code={}, 数据库中存在记录数={}", inspectionType.getCode(), existingRecords.size());
if (!existingRecords.isEmpty()) { if (!existingRecords.isEmpty()) {
return AjaxResult.error("检验类型编码已存在"); return AjaxResult.error("检验类型编码已存在");
@@ -119,8 +118,6 @@ public class InspectionTypeController extends BaseController {
return toAjax(result); return toAjax(result);
}); });
} catch (Exception e) { } catch (Exception e) {
log.error("新增检验类型失败code={}, 错误信息:{}", inspectionType.getCode(), e.getMessage(), e);
// 捕获唯一性约束冲突异常 // 捕获唯一性约束冲突异常
if (e.getMessage().contains("uk_inspection_type_code") || if (e.getMessage().contains("uk_inspection_type_code") ||
e.getMessage().contains("duplicate key value") || e.getMessage().contains("duplicate key value") ||

View File

@@ -0,0 +1,78 @@
package com.openhis.web.lab.controller;
import com.core.common.core.domain.R;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentSelParam;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentUpDto;
import com.openhis.web.lab.appservice.ILabActivityDefinitionAppService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* 检验项目维护 Controller独立操作 lab_activity_definition 表)
* 路径前缀:/lab/activity-definition
*/
@RestController
@RequestMapping("/lab/activity-definition")
@Slf4j
public class LabActivityDefinitionController {
@Resource
private ILabActivityDefinitionAppService labActivityDefinitionAppService;
/**
* 分页查询检验项目列表
*/
@GetMapping("/page")
public R<?> getPage(DiagnosisTreatmentSelParam selParam,
@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest request) {
return labActivityDefinitionAppService.getLabActivityDefinitionPage(selParam, searchKey, pageNo, pageSize, request);
}
/**
* 根据id查询检验项目详情
*/
@GetMapping("/one")
public R<?> getOne(@RequestParam Long id) {
return labActivityDefinitionAppService.getLabActivityDefinitionOne(id);
}
/**
* 新增检验项目
*/
@PostMapping("/add")
public R<?> add(@Validated @RequestBody DiagnosisTreatmentUpDto dto) {
return labActivityDefinitionAppService.addLabActivityDefinition(dto);
}
/**
* 编辑检验项目
*/
@PutMapping("/edit")
public R<?> edit(@RequestBody DiagnosisTreatmentUpDto dto) {
return labActivityDefinitionAppService.editLabActivityDefinition(dto);
}
/**
* 停用检验项目
*/
@PutMapping("/stop")
public R<?> stop(@RequestBody List<Long> ids) {
return labActivityDefinitionAppService.stopLabActivityDefinition(ids);
}
/**
* 启用检验项目
*/
@PutMapping("/start")
public R<?> start(@RequestBody List<Long> ids) {
return labActivityDefinitionAppService.startLabActivityDefinition(ids);
}
}

View File

@@ -55,6 +55,8 @@ import com.openhis.web.paymentmanage.dto.CancelPaymentDto;
import com.openhis.web.paymentmanage.dto.Clinic2207OrderResultDto; import com.openhis.web.paymentmanage.dto.Clinic2207OrderResultDto;
import com.openhis.web.paymentmanage.mapper.PaymentMapper; import com.openhis.web.paymentmanage.mapper.PaymentMapper;
import com.openhis.web.personalization.dto.ActivityDeviceDto; import com.openhis.web.personalization.dto.ActivityDeviceDto;
import com.openhis.triageandqueuemanage.domain.TriageQueueItem;
import com.openhis.triageandqueuemanage.service.TriageQueueItemService;
import com.openhis.workflow.domain.ServiceRequest; import com.openhis.workflow.domain.ServiceRequest;
import com.openhis.workflow.service.IDeviceDispenseService; import com.openhis.workflow.service.IDeviceDispenseService;
import com.openhis.workflow.service.IDeviceRequestService; import com.openhis.workflow.service.IDeviceRequestService;
@@ -81,9 +83,11 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -149,6 +153,8 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
@Resource @Resource
private OutpatientRegistrationAppMapper outpatientRegistrationAppMapper; private OutpatientRegistrationAppMapper outpatientRegistrationAppMapper;
@Resource @Resource
private TriageQueueItemService triageQueueItemService;
@Resource
private IRegService iRegService; private IRegService iRegService;
@Resource @Resource
private IPatientService iPatientService; private IPatientService iPatientService;
@@ -255,7 +261,9 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
// 账户id对应的账单列表 // 账户id对应的账单列表
Map<Long, List<ChargeItem>> chargeItemMapByAccountId Map<Long, List<ChargeItem>> chargeItemMapByAccountId
= chargeItemList.stream().collect(Collectors.groupingBy(ChargeItem::getAccountId)); = chargeItemList.stream()
.filter(item -> item.getAccountId() != null)
.collect(Collectors.groupingBy(ChargeItem::getAccountId));
// 查询合同信息 // 查询合同信息
List<Contract> contractList = contractService.list(); List<Contract> contractList = contractService.list();
@@ -1928,6 +1936,80 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
// 就诊ID // 就诊ID
Long encounterId = iEncounterService.saveEncounterByRegister(encounter); Long encounterId = iEncounterService.saveEncounterByRegister(encounter);
// 创建 triage_queue_item 队列记录
try {
Integer tenantId = encounter.getTenantId() != null ? encounter.getTenantId() : SecurityUtils.getLoginUser().getTenantId();
LocalDate queueDate = LocalDate.now();
// 查询当前科室当天的最大排队序号
Integer maxOrder = triageQueueItemService.list(
new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getOrganizationId, encounter.getOrganizationId())
.eq(TriageQueueItem::getQueueDate, queueDate)
.eq(TriageQueueItem::getDeleteFlag, "0")
.ne(TriageQueueItem::getStatus, "COMPLETED")
).stream()
.map(TriageQueueItem::getQueueOrder)
.filter(Objects::nonNull)
.max(Integer::compareTo)
.orElse(0);
// 获取患者信息
Patient patient = iPatientService.getById(encounter.getPatientId());
String patientName = patient != null ? patient.getName() : null;
// 获取挂号医生信息
String practitionerName = null;
Long queuePractitionerId = null;
if (encounterParticipantFormData.getPractitionerId() != null) {
Practitioner practitioner = iPractitionerService.getById(encounterParticipantFormData.getPractitionerId());
if (practitioner != null) {
practitionerName = practitioner.getName();
queuePractitionerId = practitioner.getId();
}
}
// 获取科室信息
Organization organization = iOrganizationService.getById(encounter.getOrganizationId());
String organizationName = organization != null ? organization.getName() : null;
// 获取服务项目信息(挂号类型)
String healthcareName = null;
if (encounter.getServiceTypeId() != null) {
HealthcareService healthcareService = healthcareServiceService.getById(encounter.getServiceTypeId());
if (healthcareService != null) {
healthcareName = healthcareService.getName();
}
}
// 创建队列项
TriageQueueItem queueItem = new TriageQueueItem()
.setTenantId(tenantId)
.setQueueDate(queueDate)
.setOrganizationId(encounter.getOrganizationId())
.setOrganizationName(organizationName)
.setEncounterId(encounterId)
.setPatientId(encounter.getPatientId())
.setPatientName(patientName)
.setHealthcareName(healthcareName)
.setPractitionerId(queuePractitionerId)
.setPractitionerName(practitionerName)
.setRoomNo(null)
.setStatus("WAITING")
.setQueueOrder(maxOrder + 1)
.setDeleteFlag("0")
.setCreateTime(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS))
.setUpdateTime(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS));
triageQueueItemService.save(queueItem);
logger.info("挂号时创建队列记录成功encounterId={}, queueItemId={}", encounterId, queueItem.getId());
} catch (Exception e) {
logger.error("挂号时创建队列记录失败encounterId={}", encounterId, e);
// 队列记录创建失败不影响挂号流程
}
// 保存就诊位置信息 // 保存就诊位置信息
// 挂号时不选Location了 // 挂号时不选Location了
// encounterLocationFormData.setEncounterId(encounterId); // encounterLocationFormData.setEncounterId(encounterId);
@@ -2095,7 +2177,7 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
adviceBaseDto.setAdviceDefinitionId(activityDeviceDto.getDevActId()); adviceBaseDto.setAdviceDefinitionId(activityDeviceDto.getDevActId());
// 对应的诊疗医嘱信息 // 对应的诊疗医嘱信息
AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null, AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null,
null, null, organizationId, 1, 1, Whether.NO.getValue(), List.of(1, 2, 3), null).getRecords().get(0); null, null, organizationId, 1, 1, Whether.NO.getValue(), List.of(1, 2, 3), null, null).getRecords().get(0);
// 价格信息 // 价格信息
if (activityAdviceBaseDto != null) { if (activityAdviceBaseDto != null) {
// 费用定价 // 费用定价
@@ -2251,7 +2333,9 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
= iChargeItemService.getChargeItemBaseInfoByIds(prePaymentDto.getChargeItemIds()); = iChargeItemService.getChargeItemBaseInfoByIds(prePaymentDto.getChargeItemIds());
Map<String, List<ChargeItemBaseInfoDto>> chargeItemKVByContractNo Map<String, List<ChargeItemBaseInfoDto>> chargeItemKVByContractNo
= chargeItemBaseInfoByIds.stream().collect(Collectors.groupingBy(ChargeItemBaseInfoDto::getContractNo)); = chargeItemBaseInfoByIds.stream()
.filter(dto -> dto.getContractNo() != null && !dto.getContractNo().isEmpty())
.collect(Collectors.groupingBy(ChargeItemBaseInfoDto::getContractNo));
List<InpatientPreSettleDto> yb2303OutputSetInfos = new ArrayList<>(); List<InpatientPreSettleDto> yb2303OutputSetInfos = new ArrayList<>();
Yb2303OutputSetInfo yb2303OutputSetInfo; Yb2303OutputSetInfo yb2303OutputSetInfo;
@@ -2379,13 +2463,17 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
List<ChargeItemBaseInfoDto> chargeItemBaseInfoByIds List<ChargeItemBaseInfoDto> chargeItemBaseInfoByIds
= iChargeItemService.getChargeItemBaseInfoByIds(paymentDto.getChargeItemIds()); = iChargeItemService.getChargeItemBaseInfoByIds(paymentDto.getChargeItemIds());
Map<String, List<ChargeItemBaseInfoDto>> chargeItemKVByContractNo Map<String, List<ChargeItemBaseInfoDto>> chargeItemKVByContractNo
= chargeItemBaseInfoByIds.stream().collect(Collectors.groupingBy(ChargeItemBaseInfoDto::getContractNo)); = chargeItemBaseInfoByIds.stream()
.filter(dto -> dto.getContractNo() != null && !dto.getContractNo().isEmpty())
.collect(Collectors.groupingBy(ChargeItemBaseInfoDto::getContractNo));
List<Account> accountList = iAccountService.getAccountListByEncounter(paymentDto.getEncounterId()); List<Account> accountList = iAccountService.getAccountListByEncounter(paymentDto.getEncounterId());
if (accountList.isEmpty()) { if (accountList.isEmpty()) {
throw new ServiceException("未查询到账户信息"); throw new ServiceException("未查询到账户信息");
} }
Map<Long, List<Account>> accountKVById = accountList.stream().collect(Collectors.groupingBy(Account::getId)); Map<Long, List<Account>> accountKVById = accountList.stream()
.filter(acc -> acc.getId() != null)
.collect(Collectors.groupingBy(Account::getId));
com.openhis.financial.model.PaymentResult paymentResult; com.openhis.financial.model.PaymentResult paymentResult;
List<com.openhis.financial.model.PaymentResult> paymentResultList = new ArrayList<>(); List<com.openhis.financial.model.PaymentResult> paymentResultList = new ArrayList<>();
@@ -2395,7 +2483,9 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
// <3>收费详情按照收费批次进行分组后结算 // <3>收费详情按照收费批次进行分组后结算
Map<Long, List<PaymentRecDetail>> payTransNoMap Map<Long, List<PaymentRecDetail>> payTransNoMap
= paymentRecDetails.stream().collect(Collectors.groupingBy(PaymentRecDetail::getAccountId)); = paymentRecDetails.stream()
.filter(detail -> detail.getAccountId() != null)
.collect(Collectors.groupingBy(PaymentRecDetail::getAccountId));
for (Map.Entry<Long, List<PaymentRecDetail>> stringListEntry : payTransNoMap.entrySet()) { for (Map.Entry<Long, List<PaymentRecDetail>> stringListEntry : payTransNoMap.entrySet()) {
// paymentResult = new PaymentResult(); // paymentResult = new PaymentResult();

View File

@@ -197,7 +197,7 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
// 医嘱下拉详细信息 // 医嘱下拉详细信息
List<AdviceBaseDto> personalRecords = List<AdviceBaseDto> personalRecords =
iDoctorStationAdviceAppService.getAdviceBaseInfo(null, null, null, orderDefinitionIdParamList, iDoctorStationAdviceAppService.getAdviceBaseInfo(null, null, null, orderDefinitionIdParamList,
organizationId, 1, 100, Whether.NO.getValue(), List.of(1, 2, 3), null).getRecords(); organizationId, 1, 100, Whether.NO.getValue(), List.of(1, 2, 3), null, null).getRecords();
// 创建AdviceBaseDto的映射以adviceDefinitionId为key // 创建AdviceBaseDto的映射以adviceDefinitionId为key
Map<Long, AdviceBaseDto> adviceMap = personalRecords.stream().collect(Collectors Map<Long, AdviceBaseDto> adviceMap = personalRecords.stream().collect(Collectors
.toMap(AdviceBaseDto::getAdviceDefinitionId, advice -> advice, (existing, replacement) -> existing // 如果有重复key保留第一个 .toMap(AdviceBaseDto::getAdviceDefinitionId, advice -> advice, (existing, replacement) -> existing // 如果有重复key保留第一个
@@ -248,7 +248,7 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
// 医嘱下拉详细信息 // 医嘱下拉详细信息
List<AdviceBaseDto> personalRecords = List<AdviceBaseDto> personalRecords =
iDoctorStationAdviceAppService.getAdviceBaseInfo(null, null, null, orderDefinitionIdParamList, iDoctorStationAdviceAppService.getAdviceBaseInfo(null, null, null, orderDefinitionIdParamList,
organizationId, 1, 100, Whether.NO.getValue(), List.of(1, 2, 3), null).getRecords(); organizationId, 1, 100, Whether.NO.getValue(), List.of(1, 2, 3), null, null).getRecords();
// 创建AdviceBaseDto的映射以adviceDefinitionId为key // 创建AdviceBaseDto的映射以adviceDefinitionId为key
Map<Long, AdviceBaseDto> adviceMap = personalRecords.stream().collect(Collectors Map<Long, AdviceBaseDto> adviceMap = personalRecords.stream().collect(Collectors
.toMap(AdviceBaseDto::getAdviceDefinitionId, advice -> advice, (existing, replacement) -> existing // 如果有重复key保留第一个 .toMap(AdviceBaseDto::getAdviceDefinitionId, advice -> advice, (existing, replacement) -> existing // 如果有重复key保留第一个
@@ -297,7 +297,7 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
// 医嘱下拉详细信息 // 医嘱下拉详细信息
List<AdviceBaseDto> personalRecords = List<AdviceBaseDto> personalRecords =
iDoctorStationAdviceAppService.getAdviceBaseInfo(null, null, null, orderDefinitionIdParamList, iDoctorStationAdviceAppService.getAdviceBaseInfo(null, null, null, orderDefinitionIdParamList,
organizationId, 1, 100, Whether.NO.getValue(), List.of(1, 2, 3), null).getRecords(); organizationId, 1, 100, Whether.NO.getValue(), List.of(1, 2, 3), null, null).getRecords();
// 创建AdviceBaseDto的映射以adviceDefinitionId为key // 创建AdviceBaseDto的映射以adviceDefinitionId为key
Map<Long, AdviceBaseDto> adviceMap = personalRecords.stream().collect(Collectors Map<Long, AdviceBaseDto> adviceMap = personalRecords.stream().collect(Collectors
.toMap(AdviceBaseDto::getAdviceDefinitionId, advice -> advice, (existing, replacement) -> existing // 如果有重复key保留第一个 .toMap(AdviceBaseDto::getAdviceDefinitionId, advice -> advice, (existing, replacement) -> existing // 如果有重复key保留第一个

View File

@@ -8,6 +8,7 @@ import com.core.common.exception.ServiceException;
import com.core.common.utils.AssignSeqUtil; import com.core.common.utils.AssignSeqUtil;
import com.core.common.utils.MessageUtils; import com.core.common.utils.MessageUtils;
import com.core.common.utils.SecurityUtils; import com.core.common.utils.SecurityUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.openhis.administration.domain.ChargeItem; import com.openhis.administration.domain.ChargeItem;
import com.openhis.administration.service.IChargeItemService; import com.openhis.administration.service.IChargeItemService;
import com.openhis.common.constant.CommonConstants; import com.openhis.common.constant.CommonConstants;
@@ -28,8 +29,10 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -132,6 +135,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
ChargeItem chargeItem; ChargeItem chargeItem;
// 诊疗集合 // 诊疗集合
List<ActivitySaveDto> activityList = requestFormSaveDto.getActivityList(); List<ActivitySaveDto> activityList = requestFormSaveDto.getActivityList();
log.info("保存申请单typeCode={}, activityListSize={}, encounterId={}", typeCode, activityList != null ? activityList.size() : 0, encounterId);
// 诊疗执行科室配置 // 诊疗执行科室配置
List<ActivityOrganizationConfigDto> activityOrganizationConfig = List<ActivityOrganizationConfigDto> activityOrganizationConfig =
requestFormManageAppMapper.getActivityOrganizationConfig(typeCode); requestFormManageAppMapper.getActivityOrganizationConfig(typeCode);
@@ -211,6 +215,189 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
} }
} }
// 如果是手术申请单,需要额外生成手术医嘱
log.info("【调试】判断手术医嘱生成条件: typeCode={}, PROCEDURE.code={}, typeCode类型={}, PROCEDURE.code类型={}",
typeCode, ActivityDefCategory.PROCEDURE.getCode(),
typeCode != null ? typeCode.getClass().getName() : "null",
ActivityDefCategory.PROCEDURE.getCode().getClass().getName());
boolean isProcedure = ActivityDefCategory.PROCEDURE.getCode().equals(typeCode);
log.info("【调试】判断结果: isProcedure={}, typeCode字符串={}, PROCEDURE.code字符串={}",
isProcedure,
typeCode != null ? "'" + typeCode + "'" : "null",
"'" + ActivityDefCategory.PROCEDURE.getCode() + "'");
if (isProcedure) {
log.info("开始生成手术医嘱encounterId={}, patientId={}, typeCode={}, activityListSize={}",
encounterId, patientId, typeCode, activityList != null ? activityList.size() : 0);
try {
// 从 descJson 中解析手术信息
String descJson = requestFormSaveDto.getDescJson();
Map<String, Object> descMap = null;
if (descJson != null && !descJson.isEmpty()) {
try {
ObjectMapper objectMapper = new ObjectMapper();
descMap = objectMapper.readValue(descJson, Map.class);
log.info("解析手术申请单 descJson 成功: {}", descMap);
} catch (Exception e) {
log.error("解析手术申请单 descJson 失败: {}", descJson, e);
}
} else {
log.warn("手术申请单 descJson 为空");
}
// 获取手术信息
String surgeryName = descMap != null ? (String) descMap.get("surgeryName") : null;
String surgeryCode = descMap != null ? (String) descMap.get("surgeryCode") : null;
String surgeryFee = descMap != null ? (String) descMap.get("surgeryFee") : null;
String anesthesiaFee = descMap != null ? (String) descMap.get("anesthesiaFee") : null;
String plannedTime = descMap != null ? (String) descMap.get("plannedTime") : null;
String surgeryIndication = descMap != null ? (String) descMap.get("surgeryIndication") : null;
String preoperativeDiagnosis = descMap != null ? (String) descMap.get("preoperativeDiagnosis") : null;
// 🔧 BugFix#318: 从 activityList 获取手术项目名称
String adviceDefinitionName = null;
if (activityList != null && !activityList.isEmpty()) {
adviceDefinitionName = activityList.get(0).getAdviceDefinitionName();
log.info("从 activityList 获取手术项目名称: {}", adviceDefinitionName);
}
log.info("手术信息: surgeryName={}, surgeryCode={}, surgeryFee={}, anesthesiaFee={}, adviceDefinitionName={}",
surgeryName, surgeryCode, surgeryFee, anesthesiaFee, adviceDefinitionName);
// 生成手术医嘱
ServiceRequest surgeryServiceRequest = new ServiceRequest();
surgeryServiceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());
surgeryServiceRequest.setBusNo(String.format("%04d", (int) (Math.random() * 10000)));
surgeryServiceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
surgeryServiceRequest.setPrescriptionNo(prescriptionNo);
surgeryServiceRequest.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue());
surgeryServiceRequest.setQuantity(BigDecimal.valueOf(1));
surgeryServiceRequest.setUnitCode("");
surgeryServiceRequest.setCategoryEnum(4); // 4-手术
// 优先从 activityList 获取手术 ID
if (activityList != null && !activityList.isEmpty()) {
Long activityId = activityList.get(0).getAdviceDefinitionId();
surgeryServiceRequest.setActivityId(activityId);
log.info("从 activityList 获取手术ID: {}", activityId);
} else {
log.warn("activityList 为空无法获取手术ID");
}
surgeryServiceRequest.setPatientId(patientId);
surgeryServiceRequest.setRequesterId(practitionerId);
surgeryServiceRequest.setEncounterId(encounterId);
surgeryServiceRequest.setAuthoredTime(curDate);
surgeryServiceRequest.setOrgId(orgId);
// 设置手术相关信息到 contentJson 字段
Map<String, String> contentMap = new java.util.HashMap<>();
// 🔧 BugFix#318: 优先使用 activityList 中的手术项目名称
if (adviceDefinitionName != null && !adviceDefinitionName.isEmpty()) {
contentMap.put("surgeryName", adviceDefinitionName);
} else if (surgeryName != null && !surgeryName.isEmpty()) {
contentMap.put("surgeryName", surgeryName);
}
if (surgeryCode != null && !surgeryCode.isEmpty()) {
contentMap.put("surgeryCode", surgeryCode);
}
if (plannedTime != null && !plannedTime.isEmpty()) {
contentMap.put("plannedTime", plannedTime);
}
if (surgeryIndication != null && !surgeryIndication.isEmpty()) {
contentMap.put("surgeryIndication", surgeryIndication);
}
if (preoperativeDiagnosis != null && !preoperativeDiagnosis.isEmpty()) {
contentMap.put("preoperativeDiagnosis", preoperativeDiagnosis);
}
if (!contentMap.isEmpty()) {
try {
ObjectMapper objectMapper = new ObjectMapper();
surgeryServiceRequest.setContentJson(objectMapper.writeValueAsString(contentMap));
} catch (Exception e) {
log.error("序列化手术信息失败", e);
}
}
try {
iServiceRequestService.save(surgeryServiceRequest);
log.info("手术医嘱生成成功serviceRequestId={}, prescriptionNo={}", surgeryServiceRequest.getId(), prescriptionNo);
} catch (Exception e) {
log.error("保存手术医嘱失败", e);
throw new ServiceException("保存手术医嘱失败: " + e.getMessage());
}
// 生成手术收费项目
try {
ChargeItem surgeryChargeItem = new ChargeItem();
surgeryChargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue());
surgeryChargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(surgeryServiceRequest.getBusNo()));
surgeryChargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
surgeryChargeItem.setPatientId(patientId);
surgeryChargeItem.setContextEnum(6); // 6-手术
surgeryChargeItem.setEncounterId(encounterId);
surgeryChargeItem.setEntererId(practitionerId);
surgeryChargeItem.setEnteredDate(curDate);
surgeryChargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST);
surgeryChargeItem.setServiceId(surgeryServiceRequest.getId());
surgeryChargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION);
// 优先从 activityList 获取 productId
if (activityList != null && !activityList.isEmpty()) {
surgeryChargeItem.setProductId(activityList.get(0).getAdviceDefinitionId());
surgeryChargeItem.setAccountId(activityList.get(0).getAccountId());
}
surgeryChargeItem.setRequestingOrgId(orgId);
surgeryChargeItem.setQuantityValue(BigDecimal.valueOf(1));
surgeryChargeItem.setQuantityUnit("");
// 设置手术费用
if (surgeryFee != null && !surgeryFee.isEmpty()) {
try {
surgeryChargeItem.setUnitPrice(new BigDecimal(surgeryFee));
surgeryChargeItem.setTotalPrice(new BigDecimal(surgeryFee));
} catch (NumberFormatException e) {
log.warn("手术费用格式不正确:{}", surgeryFee);
}
}
iChargeItemService.save(surgeryChargeItem);
log.info("手术收费项目生成成功chargeItemId={}", surgeryChargeItem.getId());
} catch (Exception e) {
log.error("生成手术收费项目失败", e);
throw new ServiceException("生成手术收费项目失败: " + e.getMessage());
}
// 如果存在麻醉费用,生成麻醉收费项目
if (anesthesiaFee != null && !anesthesiaFee.isEmpty()) {
try {
BigDecimal anesthesiaFeeAmount = new BigDecimal(anesthesiaFee);
if (anesthesiaFeeAmount.compareTo(BigDecimal.ZERO) > 0) {
ChargeItem anesthesiaChargeItem = new ChargeItem();
anesthesiaChargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue());
anesthesiaChargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(surgeryServiceRequest.getBusNo()));
anesthesiaChargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
anesthesiaChargeItem.setPatientId(patientId);
anesthesiaChargeItem.setContextEnum(3); // 3-诊疗
anesthesiaChargeItem.setEncounterId(encounterId);
anesthesiaChargeItem.setEntererId(practitionerId);
anesthesiaChargeItem.setEnteredDate(curDate);
anesthesiaChargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST);
anesthesiaChargeItem.setServiceId(surgeryServiceRequest.getId());
anesthesiaChargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION);
anesthesiaChargeItem.setRequestingOrgId(orgId);
anesthesiaChargeItem.setQuantityValue(BigDecimal.valueOf(1));
anesthesiaChargeItem.setQuantityUnit("");
anesthesiaChargeItem.setUnitPrice(anesthesiaFeeAmount);
anesthesiaChargeItem.setTotalPrice(anesthesiaFeeAmount);
iChargeItemService.save(anesthesiaChargeItem);
log.info("麻醉收费项目生成成功");
}
} catch (NumberFormatException e) {
log.warn("麻醉费用格式不正确:{}", anesthesiaFee);
}
}
} catch (Exception e) {
log.error("生成手术医嘱过程中发生异常", e);
throw e;
}
} else {
log.info("不是手术申请单跳过手术医嘱生成typeCode={}", typeCode);
}
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[] {"申请单"})); return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[] {"申请单"}));
} }

View File

@@ -161,7 +161,7 @@ public class SpecialAdviceAppServiceImpl implements ISpecialAdviceAppService {
adviceBaseDto.setAdviceDefinitionId(definitionId); // 医嘱定义id adviceBaseDto.setAdviceDefinitionId(definitionId); // 医嘱定义id
// 对应的诊疗医嘱信息 // 对应的诊疗医嘱信息
activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null, null, activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null, null,
null, null, 1, 1, Whether.NO.getValue(), List.of(3), null).getRecords().get(0); null, null, 1, 1, Whether.NO.getValue(), List.of(3), null, null).getRecords().get(0);
// 逻辑1---------------------直接新增 // 逻辑1---------------------直接新增
longServiceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());// 请求状态 longServiceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());// 请求状态
longServiceRequest.setOccurrenceStartTime(startTime); // 医嘱开始时间 longServiceRequest.setOccurrenceStartTime(startTime); // 医嘱开始时间
@@ -208,7 +208,7 @@ public class SpecialAdviceAppServiceImpl implements ISpecialAdviceAppService {
adviceBaseDto.setAdviceDefinitionId(definitionId); // 医嘱定义id adviceBaseDto.setAdviceDefinitionId(definitionId); // 医嘱定义id
// 对应的诊疗医嘱信息 // 对应的诊疗医嘱信息
activityAdviceBaseDto = iDoctorStationAdviceAppService activityAdviceBaseDto = iDoctorStationAdviceAppService
.getAdviceBaseInfo(adviceBaseDto, null, null, null, null, 1, 1, Whether.NO.getValue(), List.of(3), null) .getAdviceBaseInfo(adviceBaseDto, null, null, null, null, 1, 1, Whether.NO.getValue(), List.of(3), null, null)
.getRecords().get(0); .getRecords().get(0);
longServiceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());// 请求状态 longServiceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());// 请求状态
@@ -348,7 +348,7 @@ public class SpecialAdviceAppServiceImpl implements ISpecialAdviceAppService {
adviceBaseDto.setAdviceDefinitionId(transferOrganizationDefinitionId); // 医嘱定义id adviceBaseDto.setAdviceDefinitionId(transferOrganizationDefinitionId); // 医嘱定义id
// 转科的医嘱信息 // 转科的医嘱信息
AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService
.getAdviceBaseInfo(adviceBaseDto, null, null, null, null, 1, 1, Whether.NO.getValue(), List.of(3), null) .getAdviceBaseInfo(adviceBaseDto, null, null, null, null, 1, 1, Whether.NO.getValue(), List.of(3), null, null)
.getRecords().get(0); .getRecords().get(0);
// 保存转科医嘱请求 // 保存转科医嘱请求
ServiceRequest serviceRequest = new ServiceRequest(); ServiceRequest serviceRequest = new ServiceRequest();
@@ -430,7 +430,7 @@ public class SpecialAdviceAppServiceImpl implements ISpecialAdviceAppService {
// 出院的医嘱信息 // 出院的医嘱信息
AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(null, null, null, AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(null, null, null,
List.of(transferOrganizationDefinitionId), null, 1, 1, Whether.NO.getValue(), List.of(3), null).getRecords() List.of(transferOrganizationDefinitionId), null, 1, 1, Whether.NO.getValue(), List.of(3), null, null).getRecords()
.get(0); .get(0);
// 保存出院医嘱请求 // 保存出院医嘱请求
ServiceRequest serviceRequest = new ServiceRequest(); ServiceRequest serviceRequest = new ServiceRequest();

View File

@@ -72,6 +72,7 @@ public class RequestFormManageController {
*/ */
@PostMapping(value = "/save-surgery") @PostMapping(value = "/save-surgery")
public R<?> saveSurgeryRequestForm(@RequestBody RequestFormSaveDto requestFormSaveDto) { public R<?> saveSurgeryRequestForm(@RequestBody RequestFormSaveDto requestFormSaveDto) {
log.info("【Controller】保存手术申请单typeCode={}", ActivityDefCategory.PROCEDURE.getCode());
return iRequestFormManageAppService.saveRequestForm(requestFormSaveDto, return iRequestFormManageAppService.saveRequestForm(requestFormSaveDto,
ActivityDefCategory.PROCEDURE.getCode()); ActivityDefCategory.PROCEDURE.getCode());
} }

View File

@@ -10,6 +10,10 @@ import java.time.LocalDate;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class RequestFormDto { public class RequestFormDto {
/**
* 手术单号
*/
private String surgeryNo;
/** /**
* 申请时间开始 * 申请时间开始
*/ */

View File

@@ -93,4 +93,9 @@ public class RequestFormPageDto {
* 手术等级 * 手术等级
*/ */
private Integer surgeryLevel; private Integer surgeryLevel;
/**
* 就诊卡号
*/
private String identifierNo;
} }

View File

@@ -93,16 +93,19 @@
T7.med_type_code, T7.med_type_code,
T8.contract_name, T8.contract_name,
CASE CASE
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
WHEN T1.context_enum = #{activity} THEN T2."name" WHEN T1.context_enum = #{activity} THEN T2."name"
WHEN T1.context_enum = #{medication} THEN T3."name" WHEN T1.context_enum = #{medication} THEN T3."name"
WHEN T1.context_enum = #{device} THEN T4."name" WHEN T1.context_enum = #{device} THEN T4."name"
END AS item_name, END AS item_name,
CASE CASE
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN NULL
WHEN T1.context_enum = #{activity} THEN T2.yb_no WHEN T1.context_enum = #{activity} THEN T2.yb_no
WHEN T1.context_enum = #{medication} THEN T3.yb_no WHEN T1.context_enum = #{medication} THEN T3.yb_no
WHEN T1.context_enum = #{device} THEN T4.yb_no WHEN T1.context_enum = #{device} THEN T4.yb_no
END AS yb_no, END AS yb_no,
CASE CASE
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.id
WHEN T1.context_enum = #{activity} THEN T2.id WHEN T1.context_enum = #{activity} THEN T2.id
WHEN T1.context_enum = #{medication} THEN T3.id WHEN T1.context_enum = #{medication} THEN T3.id
WHEN T1.context_enum = #{device} THEN T4.id WHEN T1.context_enum = #{device} THEN T4.id
@@ -120,6 +123,10 @@
ON T1.context_enum = #{device} ON T1.context_enum = #{device}
AND T1.product_id = T4.id AND T1.product_id = T4.id
AND T4.delete_flag = '0' AND T4.delete_flag = '0'
LEFT JOIN cli_surgery AS T9
ON T1.product_table = 'cli_surgery'
AND T1.product_id = T9.id
AND T9.delete_flag = '0'
LEFT JOIN fin_payment_reconciliation AS T5 LEFT JOIN fin_payment_reconciliation AS T5
ON T1.id::TEXT = ANY(string_to_array(T5.charge_item_ids, ',')) ON T1.id::TEXT = ANY(string_to_array(T5.charge_item_ids, ','))
AND T5.delete_flag = '0' AND T5.delete_flag = '0'
@@ -186,16 +193,19 @@
T7.med_type_code, T7.med_type_code,
T8.contract_name, T8.contract_name,
CASE CASE
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
WHEN T1.context_enum = #{activity} THEN T2."name" WHEN T1.context_enum = #{activity} THEN T2."name"
WHEN T1.context_enum = #{medication} THEN T3."name" WHEN T1.context_enum = #{medication} THEN T3."name"
WHEN T1.context_enum = #{device} THEN T4."name" WHEN T1.context_enum = #{device} THEN T4."name"
END AS item_name, END AS item_name,
CASE CASE
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN NULL
WHEN T1.context_enum = #{activity} THEN T2.yb_no WHEN T1.context_enum = #{activity} THEN T2.yb_no
WHEN T1.context_enum = #{medication} THEN T3.yb_no WHEN T1.context_enum = #{medication} THEN T3.yb_no
WHEN T1.context_enum = #{device} THEN T4.yb_no WHEN T1.context_enum = #{device} THEN T4.yb_no
END AS yb_no, END AS yb_no,
CASE CASE
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.id
WHEN T1.context_enum = #{activity} THEN T2.id WHEN T1.context_enum = #{activity} THEN T2.id
WHEN T1.context_enum = #{medication} THEN T3.id WHEN T1.context_enum = #{medication} THEN T3.id
WHEN T1.context_enum = #{device} THEN T4.id WHEN T1.context_enum = #{device} THEN T4.id
@@ -214,6 +224,10 @@
ON T1.context_enum = #{device} ON T1.context_enum = #{device}
AND T1.product_id = T4.id AND T1.product_id = T4.id
AND T4.delete_flag = '0' AND T4.delete_flag = '0'
LEFT JOIN cli_surgery AS T9
ON T1.product_table = 'cli_surgery'
AND T1.product_id = T9.id
AND T9.delete_flag = '0'
LEFT JOIN fin_payment_reconciliation AS T5 LEFT JOIN fin_payment_reconciliation AS T5
ON T1.id::TEXT = ANY(string_to_array(T5.charge_item_ids, ',')) ON T1.id::TEXT = ANY(string_to_array(T5.charge_item_ids, ','))
AND T5.delete_flag = '0' AND T5.delete_flag = '0'

View File

@@ -13,6 +13,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="patientAge" column="patient_age" /> <result property="patientAge" column="patient_age" />
<result property="encounterId" column="encounter_id" /> <result property="encounterId" column="encounter_id" />
<result property="encounterNo" column="encounter_no" /> <result property="encounterNo" column="encounter_no" />
<result property="patientCardNo" column="patient_card_no" />
<result property="applyDoctorId" column="apply_doctor_id" /> <result property="applyDoctorId" column="apply_doctor_id" />
<result property="applyDoctorName" column="apply_doctor_name" /> <result property="applyDoctorName" column="apply_doctor_name" />
<result property="applyDeptId" column="apply_dept_id" /> <result property="applyDeptId" column="apply_dept_id" />
@@ -79,6 +80,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
EXTRACT(YEAR FROM AGE(p.birth_date)) as patient_age, EXTRACT(YEAR FROM AGE(p.birth_date)) as patient_age,
s.encounter_id, s.encounter_id,
e.bus_no as encounter_no, e.bus_no as encounter_no,
pi.identifier_no as patient_card_no,
s.apply_doctor_id, s.apply_doctor_id,
COALESCE(s.apply_doctor_name, apply_doc.name) as apply_doctor_name, COALESCE(s.apply_doctor_name, apply_doc.name) as apply_doctor_name,
s.apply_dept_id, s.apply_dept_id,
@@ -177,6 +179,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
FROM cli_surgery s FROM cli_surgery s
LEFT JOIN adm_patient p ON s.patient_id = p.id LEFT JOIN adm_patient p ON s.patient_id = p.id
LEFT JOIN adm_encounter e ON s.encounter_id = e.id LEFT JOIN adm_encounter e ON s.encounter_id = e.id
LEFT JOIN (
SELECT patient_id, identifier_no
FROM (
SELECT patient_id, identifier_no,
ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY create_time ASC) AS rn
FROM adm_patient_identifier
WHERE delete_flag = '0'
AND identifier_no IS NOT NULL
AND identifier_no != ''
) t
WHERE rn = 1
) pi ON s.patient_id = pi.patient_id
LEFT JOIN adm_operating_room r ON s.operating_room_id = r.id LEFT JOIN adm_operating_room r ON s.operating_room_id = r.id
LEFT JOIN adm_organization ro ON r.organization_id = ro.id LEFT JOIN adm_organization ro ON r.organization_id = ro.id
LEFT JOIN adm_organization o ON s.org_id = o.id LEFT JOIN adm_organization o ON s.org_id = o.id
@@ -248,6 +262,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
p.birth_date, p.birth_date,
<!-- 就诊编号 --> <!-- 就诊编号 -->
e.bus_no as encounter_no, e.bus_no as encounter_no,
<!-- 就诊卡号 -->
pi.identifier_no as patient_card_no,
<!-- 字典文本使用CASE WHEN避免额外JOIN --> <!-- 字典文本使用CASE WHEN避免额外JOIN -->
CASE s.surgery_type_enum CASE s.surgery_type_enum
WHEN 1 THEN '门诊手术' WHEN 1 THEN '门诊手术'
@@ -302,6 +318,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<!-- 只JOIN必要的表患者和就诊 --> <!-- 只JOIN必要的表患者和就诊 -->
LEFT JOIN adm_patient p ON s.patient_id = p.id LEFT JOIN adm_patient p ON s.patient_id = p.id
LEFT JOIN adm_encounter e ON s.encounter_id = e.id LEFT JOIN adm_encounter e ON s.encounter_id = e.id
<!-- 关联患者标识表获取就诊卡号 -->
LEFT JOIN (
SELECT patient_id, identifier_no
FROM (
SELECT patient_id, identifier_no,
ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY create_time ASC) AS rn
FROM adm_patient_identifier
WHERE delete_flag = '0'
AND identifier_no IS NOT NULL
AND identifier_no != ''
) t
WHERE rn = 1
) pi ON s.patient_id = pi.patient_id
<where> <where>
s.delete_flag = '0' s.delete_flag = '0'
<if test="ew.sqlSegment != null and ew.sqlSegment != ''"> <if test="ew.sqlSegment != null and ew.sqlSegment != ''">

View File

@@ -30,13 +30,26 @@
cs.apply_dept_name, cs.apply_dept_name,
cs.org_id, cs.org_id,
o.name AS org_name, o.name AS org_name,
cs.main_surgeon_name AS surgeon_name cs.main_surgeon_name AS surgeon_name,
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
FROM op_schedule os FROM op_schedule os
LEFT JOIN adm_patient ap ON os.patient_id = ap.id LEFT JOIN adm_patient ap ON os.patient_id = ap.id
LEFT JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0' LEFT JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0'
LEFT JOIN adm_organization o ON cs.org_id = o.id LEFT JOIN adm_organization o ON cs.org_id = o.id
LEFT JOIN sys_tenant st ON st.id = os.tenant_id LEFT JOIN sys_tenant st ON st.id = os.tenant_id
LEFT JOIN sys_user su ON su.user_id = os.creator_id LEFT JOIN sys_user su ON su.user_id = os.creator_id
LEFT JOIN (
SELECT patient_id, identifier_no
FROM (
SELECT patient_id, identifier_no,
ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY create_time ASC) AS rn
FROM adm_patient_identifier
WHERE delete_flag = '0'
AND identifier_no IS NOT NULL
AND identifier_no != ''
) t
WHERE rn = 1
) pi ON os.patient_id = pi.patient_id
<where> <where>
<if test="dto.tenantId != null"> <if test="dto.tenantId != null">
AND os.tenant_id = #{dto.tenantId} AND os.tenant_id = #{dto.tenantId}
@@ -47,8 +60,11 @@
<if test="dto.applyDeptId != null and dto.applyDeptId != ''"> <if test="dto.applyDeptId != null and dto.applyDeptId != ''">
AND cs.apply_dept_id = #{dto.applyDeptId} AND cs.apply_dept_id = #{dto.applyDeptId}
</if> </if>
<if test="dto.scheduleDate != null"> <if test="dto.scheduleDateStart != null">
AND os.schedule_date = #{dto.scheduleDate} AND DATE(os.schedule_date) &gt;= #{dto.scheduleDateStart}
</if>
<if test="dto.scheduleDateEnd != null">
AND DATE(os.schedule_date) &lt;= #{dto.scheduleDateEnd}
</if> </if>
<if test="dto.operCode != null and dto.operCode != ''"> <if test="dto.operCode != null and dto.operCode != ''">
AND os.oper_code LIKE CONCAT('%', #{dto.operCode}, '%') AND os.oper_code LIKE CONCAT('%', #{dto.operCode}, '%')
@@ -72,12 +88,25 @@
cs.main_surgeon_name AS surgeon_name, cs.main_surgeon_name AS surgeon_name,
cs.apply_doctor_name AS apply_doctor_name, cs.apply_doctor_name AS apply_doctor_name,
drf.create_time AS apply_time, drf.create_time AS apply_time,
os.surgery_nature AS surgeryType os.surgery_nature AS surgeryType,
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
FROM op_schedule os FROM op_schedule os
LEFT JOIN adm_patient ap ON os.patient_id = ap.id LEFT JOIN adm_patient ap ON os.patient_id = ap.id
LEFT JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0' LEFT JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0'
LEFT JOIN adm_organization o ON cs.org_id = o.id LEFT JOIN adm_organization o ON cs.org_id = o.id
LEFT JOIN doc_request_form drf ON drf.prescription_no=cs.surgery_no LEFT JOIN doc_request_form drf ON drf.prescription_no=cs.surgery_no
LEFT JOIN (
SELECT patient_id, identifier_no
FROM (
SELECT patient_id, identifier_no,
ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY create_time ASC) AS rn
FROM adm_patient_identifier
WHERE delete_flag = '0'
AND identifier_no IS NOT NULL
AND identifier_no != ''
) t
WHERE rn = 1
) pi ON os.patient_id = pi.patient_id
WHERE os.schedule_id = #{scheduleId} WHERE os.schedule_id = #{scheduleId}
LIMIT 1 LIMIT 1
</select> </select>
@@ -120,13 +149,26 @@
cs.apply_dept_name, cs.apply_dept_name,
cs.org_id, cs.org_id,
o.name AS org_name, o.name AS org_name,
cs.main_surgeon_name AS surgeon_name cs.main_surgeon_name AS surgeon_name,
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
FROM op_schedule os FROM op_schedule os
LEFT JOIN adm_patient ap ON os.patient_id = ap.id LEFT JOIN adm_patient ap ON os.patient_id = ap.id
LEFT JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0' LEFT JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0'
LEFT JOIN adm_organization o ON cs.org_id = o.id LEFT JOIN adm_organization o ON cs.org_id = o.id
LEFT JOIN sys_tenant st ON st.id = os.tenant_id LEFT JOIN sys_tenant st ON st.id = os.tenant_id
LEFT JOIN sys_user su ON su.user_id = os.creator_id LEFT JOIN sys_user su ON su.user_id = os.creator_id
LEFT JOIN (
SELECT patient_id, identifier_no
FROM (
SELECT patient_id, identifier_no,
ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY create_time ASC) AS rn
FROM adm_patient_identifier
WHERE delete_flag = '0'
AND identifier_no IS NOT NULL
AND identifier_no != ''
) t
WHERE rn = 1
) pi ON os.patient_id = pi.patient_id
<where> <where>
AND os.delete_flag = '0' AND os.delete_flag = '0'
<if test="dto.patientId != null"> AND os.patient_id = #{dto.patientId}</if> <if test="dto.patientId != null"> AND os.patient_id = #{dto.patientId}</if>
@@ -134,7 +176,8 @@
<if test="dto.applyId != null"> AND os.apply_id = #{dto.applyId}</if> <if test="dto.applyId != null"> AND os.apply_id = #{dto.applyId}</if>
<if test="dto.operCode != null and dto.operCode != ''"> AND os.oper_code = #{dto.operCode}</if> <if test="dto.operCode != null and dto.operCode != ''"> AND os.oper_code = #{dto.operCode}</if>
<if test="dto.operName != null and dto.operName != ''"> AND os.oper_name LIKE CONCAT('%', #{dto.operName}, '%')</if> <if test="dto.operName != null and dto.operName != ''"> AND os.oper_name LIKE CONCAT('%', #{dto.operName}, '%')</if>
<if test="dto.scheduleDate != null"> AND os.schedule_date = #{dto.scheduleDate}</if> <if test="dto.scheduleDateStart != null"> AND DATE(os.schedule_date) &gt;= #{dto.scheduleDateStart}</if>
<if test="dto.scheduleDateEnd != null"> AND DATE(os.schedule_date) &lt;= #{dto.scheduleDateEnd}</if>
<if test="dto.orgId != null and dto.orgId != ''"> AND cs.org_id = #{dto.orgId}</if> <if test="dto.orgId != null and dto.orgId != ''"> AND cs.org_id = #{dto.orgId}</if>
<if test="dto.applyDeptId != null and dto.applyDeptId != ''"> AND cs.apply_dept_id = #{dto.applyDeptId}</if> <if test="dto.applyDeptId != null and dto.applyDeptId != ''"> AND cs.apply_dept_id = #{dto.applyDeptId}</if>
<if test="dto.patientName != null and dto.patientName != ''"> AND ap.name LIKE CONCAT('%', #{dto.patientName}, '%')</if> <if test="dto.patientName != null and dto.patientName != ''"> AND ap.name LIKE CONCAT('%', #{dto.patientName}, '%')</if>

View File

@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.openhis.web.datadictionary.mapper.LabActivityDefinitionManageMapper">
<!-- 检验项目分页查询(操作 lab_activity_definition 表,无物理外键) -->
<select id="getLabActivityDefinitionPage" parameterType="java.util.Map"
resultType="com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto">
SELECT
T1.id,
T1.category_code,
T1.bus_no,
T1.name,
T1.py_str,
T1.wb_str,
T1.type_enum,
T1.permitted_unit_code,
T1.org_id,
T1.location_id,
T1.yb_flag,
T1.yb_no,
T1.yb_match_flag,
T1.status_enum,
T1.body_site_code,
T1.specimen_code,
T1.description_text,
T1.rule_id,
T1.tenant_id,
T1.chrgitm_lv,
T1.children_json,
T1.pricing_flag,
T1.sort_order,
T1.service_range,
T1.inspection_type_id,
T1.fee_package_id,
T1.sub_item_id,
T3.name AS test_type,
T5.package_name,
T6.name AS sub_item_name
FROM lab_activity_definition T1
/* 检验类型关联(逻辑关联,无外键) */
LEFT JOIN inspection_type T3
ON T1.inspection_type_id = T3.id
AND T3.valid_flag = 1
/* 费用套餐关联(逻辑关联,无外键) */
LEFT JOIN inspection_basic_information T5
ON T1.fee_package_id = T5.basic_information_id
AND T5.del_flag = false
/* 下级医技类型关联(逻辑关联,无外键) */
LEFT JOIN inspection_type T6
ON T1.sub_item_id = T6.id
AND T6.valid_flag = 1
<where>
T1.delete_flag = '0'
<if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
<choose>
<when test="ew.customSqlSegment.contains('tenant_id')">
${ew.customSqlSegment.replaceFirst('tenant_id', 'T1.tenant_id').replaceFirst('status_enum', 'T1.status_enum').replaceFirst('WHERE', 'AND')}
</when>
<otherwise>
${ew.customSqlSegment.replaceFirst('status_enum', 'T1.status_enum').replaceFirst('WHERE', 'AND')}
</otherwise>
</choose>
</if>
</where>
ORDER BY T1.id DESC
</select>
<!-- 检验项目详情 -->
<select id="getLabActivityDefinitionOne" resultType="com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto">
SELECT
T1.id,
T1.category_code,
T1.bus_no,
T1.name,
T1.py_str,
T1.wb_str,
T1.type_enum,
T1.permitted_unit_code,
T1.org_id,
T1.location_id,
T1.yb_flag,
T1.yb_no,
T1.yb_match_flag,
T1.status_enum,
T1.body_site_code,
T1.specimen_code,
T1.description_text,
T1.rule_id,
T1.tenant_id,
T1.chrgitm_lv,
T1.children_json,
T1.pricing_flag,
T1.sort_order,
T1.service_range,
T1.inspection_type_id,
T1.fee_package_id,
T1.sub_item_id,
T3.name AS test_type,
T5.package_name,
T6.name AS sub_item_name
FROM lab_activity_definition T1
LEFT JOIN inspection_type T3
ON T1.inspection_type_id = T3.id
AND T3.valid_flag = 1
LEFT JOIN inspection_basic_information T5
ON T1.fee_package_id = T5.basic_information_id
AND T5.del_flag = false
LEFT JOIN inspection_type T6
ON T1.sub_item_id = T6.id
AND T6.valid_flag = 1
<where>
T1.delete_flag = '0'
<if test="id != null">
AND T1.id = #{id}
</if>
<if test="tenantId != null">
AND T1.tenant_id = #{tenantId}
</if>
</where>
</select>
<!-- 检验项目下拉列表(轻量级) -->
<select id="getLabActivityDefinitionSimpleList" resultType="com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto">
SELECT
T1.id,
T1.bus_no,
T1.name,
T1.permitted_unit_code
FROM lab_activity_definition T1
WHERE T1.delete_flag = '0'
AND T1.status_enum = #{statusEnum}
AND T1.tenant_id = #{tenantId}
<if test="searchKey != null and searchKey != ''">
AND (
T1.name LIKE CONCAT('%', #{searchKey}, '%')
OR T1.bus_no LIKE CONCAT('%', #{searchKey}, '%')
OR T1.py_str LIKE CONCAT('%', #{searchKey}, '%')
)
</if>
ORDER BY T1.id DESC
<if test="searchKey != null and searchKey != ''">
LIMIT 1500
</if>
<if test="searchKey == null or searchKey == ''">
LIMIT 500
</if>
</select>
</mapper>

View File

@@ -101,6 +101,9 @@
<if test="pricingFlag == 1"> <if test="pricingFlag == 1">
AND 1 = 2 AND 1 = 2
</if> </if>
<if test="categoryCode != null and categoryCode != ''">
AND t1.category_code = #{categoryCode}
</if>
<if test="searchKey != null and searchKey != ''"> <if test="searchKey != null and searchKey != ''">
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%') AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
</if> </if>
@@ -468,6 +471,7 @@
T1.dose AS dose, T1.dose AS dose,
T1.dose_unit_code AS dose_unit_code, T1.dose_unit_code AS dose_unit_code,
T4.id AS charge_item_id, T4.id AS charge_item_id,
T4.unit_price AS unit_price,
T4.total_price AS total_price, T4.total_price AS total_price,
T4.status_enum AS charge_status, T4.status_enum AS charge_status,
al.id AS position_id, al.id AS position_id,
@@ -477,7 +481,9 @@
ccd.name AS condition_definition_name, ccd.name AS condition_definition_name,
T1.sort_number AS sort_number, T1.sort_number AS sort_number,
T1.based_on_id AS based_on_id, T1.based_on_id AS based_on_id,
T1.category_enum AS category_enum T1.category_enum AS category_enum,
T1.encounter_id AS encounter_id,
T1.patient_id AS patient_id
FROM med_medication_request AS T1 FROM med_medication_request AS T1
LEFT JOIN med_medication_definition AS T2 ON T2.ID = T1.medication_id LEFT JOIN med_medication_definition AS T2 ON T2.ID = T1.medication_id
AND T2.delete_flag = '0' AND T2.delete_flag = '0'
@@ -520,6 +526,7 @@
NULL AS dose, NULL AS dose,
'' AS dose_unit_code, '' AS dose_unit_code,
T3.id AS charge_item_id, T3.id AS charge_item_id,
T3.unit_price AS unit_price,
T3.total_price AS total_price, T3.total_price AS total_price,
T3.status_enum AS charge_status, T3.status_enum AS charge_status,
al.id AS position_id, al.id AS position_id,
@@ -529,7 +536,9 @@
'' AS condition_definition_name, '' AS condition_definition_name,
99 AS sort_number, 99 AS sort_number,
T1.based_on_id AS based_on_id, T1.based_on_id AS based_on_id,
T1.category_enum AS category_enum T1.category_enum AS category_enum,
T1.encounter_id AS encounter_id,
T1.patient_id AS patient_id
FROM wor_device_request AS T1 FROM wor_device_request AS T1
LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id
AND T2.delete_flag = '0' AND T2.delete_flag = '0'
@@ -547,7 +556,7 @@
AND T1.refund_device_id IS NULL AND T1.refund_device_id IS NULL
ORDER BY T1.status_enum) ORDER BY T1.status_enum)
UNION ALL UNION ALL
(SELECT 3 AS advice_type, (SELECT CASE WHEN T1.category_enum = 4 THEN 6 ELSE COALESCE(T1.category_enum, 3) END AS advice_type,
T1.id AS request_id, T1.id AS request_id,
T1.id || '-3' AS unique_key, T1.id || '-3' AS unique_key,
'' AS prescription_no, '' AS prescription_no,
@@ -558,7 +567,7 @@
null AS skin_test_flag, null AS skin_test_flag,
null AS inject_flag, null AS inject_flag,
null AS group_id, null AS group_id,
COALESCE(T2.NAME, CASE WHEN T1.content_json IS NOT NULL AND T1.content_json != '' THEN T1.content_json::json->>'adviceName' ELSE NULL END) AS advice_name, COALESCE(T2.NAME, T1.content_json::jsonb->>'surgeryName', T1.content_json::jsonb->>'adviceName') AS advice_name,
'' AS volume, '' AS volume,
'' AS lot_number, '' AS lot_number,
T1.quantity AS quantity, T1.quantity AS quantity,
@@ -569,6 +578,7 @@
NULL AS dose, NULL AS dose,
'' AS dose_unit_code, '' AS dose_unit_code,
T3.id AS charge_item_id, T3.id AS charge_item_id,
T3.unit_price AS unit_price,
T3.total_price AS total_price, T3.total_price AS total_price,
T3.status_enum AS charge_status, T3.status_enum AS charge_status,
ao.id AS position_id, ao.id AS position_id,
@@ -578,7 +588,9 @@
'' AS condition_definition_name, '' AS condition_definition_name,
99 AS sort_number, 99 AS sort_number,
T1.based_on_id AS based_on_id, T1.based_on_id AS based_on_id,
T1.category_enum AS category_enum T1.category_enum AS category_enum,
T1.encounter_id AS encounter_id,
T1.patient_id AS patient_id
FROM wor_service_request AS T1 FROM wor_service_request AS T1
LEFT JOIN wor_activity_definition AS T2 LEFT JOIN wor_activity_definition AS T2
ON T2.ID = T1.activity_id ON T2.ID = T1.activity_id

View File

@@ -7,6 +7,7 @@
<!-- 根据申请单号查询检验申请单(返回完整字段) --> <!-- 根据申请单号查询检验申请单(返回完整字段) -->
<select id="getInspectionApplyByApplyNo" resultType="com.openhis.web.doctorstation.dto.DoctorStationLabApplyDto"> <select id="getInspectionApplyByApplyNo" resultType="com.openhis.web.doctorstation.dto.DoctorStationLabApplyDto">
SELECT SELECT
id AS applicationId,
apply_no AS applyNo, apply_no AS applyNo,
patient_id AS patientId, patient_id AS patientId,
patient_name AS patientName, patient_name AS patientName,
@@ -40,20 +41,17 @@
</select> </select>
<!-- 分页查询检验申请单列表根据就诊ID查询按申请时间降序 <!-- 分页查询检验申请单列表根据就诊ID查询按申请时间降序
encounterId: 就诊ID作为查询条件查出患者idpatient_id再以患者id对申请单进行查询 直接查询申请单表,不关联明细表,避免重复记录-->
对申请单表(lab_apply)、申请单明细表(lab_apply_item)和就诊表(adm_encounter)进行联合查询(申请单号,检验项目,申请医生,申请单优先级码,申请单状态,金额)-->
<select id="getInspectionApplyListPage" resultType="com.openhis.web.doctorstation.dto.DoctorStationLabApplyDto"> <select id="getInspectionApplyListPage" resultType="com.openhis.web.doctorstation.dto.DoctorStationLabApplyDto">
SELECT t1.apply_no AS applyNo, SELECT t1.id AS applicationId,
t1.inspection_item AS inspectionItem, t1.apply_no AS applyNo,
t1.inspection_item AS itemName,
t1.apply_doc_name AS applyDocName, t1.apply_doc_name AS applyDocName,
t1.priority_code AS priorityCode, t1.priority_code AS priorityCode,
t2.item_name AS itemName,
t1.apply_status AS applyStatus, t1.apply_status AS applyStatus,
t2.item_amount AS itemAmount t1.apply_remark AS applyRemark
FROM lab_apply AS t1 FROM lab_apply AS t1
INNER JOIN adm_encounter AS t3 ON t1.patient_id::bigint = t3.patient_id INNER JOIN adm_encounter AS t3 ON t1.patient_id::bigint = t3.patient_id
LEFT JOIN lab_apply_item AS t2
ON t1.apply_no = t2.apply_no
WHERE t1.delete_flag = '0' WHERE t1.delete_flag = '0'
AND t3.id = #{encounterId} AND t3.id = #{encounterId}
ORDER BY t1.apply_time DESC ORDER BY t1.apply_time DESC

View File

@@ -203,6 +203,7 @@
T1.dose AS dose, T1.dose AS dose,
T1.dose_unit_code AS dose_unit_code, T1.dose_unit_code AS dose_unit_code,
T4.id AS charge_item_id, T4.id AS charge_item_id,
T4.unit_price AS unit_price,
T4.total_price AS total_price, T4.total_price AS total_price,
T4.status_enum AS charge_status, T4.status_enum AS charge_status,
al.id AS position_id, al.id AS position_id,
@@ -254,6 +255,7 @@
NULL AS dose, NULL AS dose,
'' AS dose_unit_code, '' AS dose_unit_code,
T3.id AS charge_item_id, T3.id AS charge_item_id,
T3.unit_price AS unit_price,
T3.total_price AS total_price, T3.total_price AS total_price,
T3.status_enum AS charge_status, T3.status_enum AS charge_status,
al.id AS position_id, al.id AS position_id,
@@ -281,7 +283,7 @@
AND T1.refund_device_id IS NULL AND T1.refund_device_id IS NULL
ORDER BY T1.status_enum) ORDER BY T1.status_enum)
UNION ALL UNION ALL
(SELECT 3 AS advice_type, (SELECT CASE WHEN T1.category_enum = 4 THEN 6 ELSE COALESCE(T1.category_enum, 3) END AS advice_type,
T1.id AS request_id, T1.id AS request_id,
T1.id || '-3' AS unique_key, T1.id || '-3' AS unique_key,
T1.requester_id AS requester_id, T1.requester_id AS requester_id,
@@ -291,7 +293,7 @@
null AS skin_test_flag, null AS skin_test_flag,
null AS inject_flag, null AS inject_flag,
null AS group_id, null AS group_id,
T2.NAME AS advice_name, COALESCE(T2.NAME, T1.content_json::jsonb->>'surgeryName') AS advice_name,
'' AS volume, '' AS volume,
'' AS lot_number, '' AS lot_number,
T1.quantity AS quantity, T1.quantity AS quantity,
@@ -302,6 +304,7 @@
NULL AS dose, NULL AS dose,
'' AS dose_unit_code, '' AS dose_unit_code,
T3.id AS charge_item_id, T3.id AS charge_item_id,
T3.unit_price AS unit_price,
T3.total_price AS total_price, T3.total_price AS total_price,
T3.status_enum AS charge_status, T3.status_enum AS charge_status,
ao.id AS position_id, ao.id AS position_id,
@@ -309,7 +312,7 @@
null AS dispense_per_duration, null AS dispense_per_duration,
1 AS part_percent, 1 AS part_percent,
'' AS condition_definition_name, '' AS condition_definition_name,
T1.therapy_enum AS therapyEnum, COALESCE(T1.therapy_enum, 2) AS therapyEnum,
99 AS sort_number, 99 AS sort_number,
T1.based_on_id AS based_on_id T1.based_on_id AS based_on_id
FROM wor_service_request AS T1 FROM wor_service_request AS T1

View File

@@ -26,12 +26,13 @@
<select id="getRequestFormDetail" resultType="com.openhis.web.regdoctorstation.dto.RequestFormDetailQueryDto"> <select id="getRequestFormDetail" resultType="com.openhis.web.regdoctorstation.dto.RequestFormDetailQueryDto">
SELECT wsr.quantity, SELECT wsr.quantity,
wsr.unit_code, wsr.unit_code,
wad.NAME AS advice_name, COALESCE(wad.NAME, wsr.content_json::jsonb->>'surgeryName') AS advice_name,
aci.total_price aci.total_price
FROM wor_service_request AS wsr FROM wor_service_request AS wsr
LEFT JOIN wor_activity_definition AS wad ON wad.ID = wsr.activity_id LEFT JOIN wor_activity_definition AS wad ON wad.ID = wsr.activity_id
AND wad.delete_flag = '0' AND wad.delete_flag = '0'
LEFT JOIN adm_charge_item AS aci ON aci.service_id = wsr.ID LEFT JOIN adm_charge_item AS aci ON aci.service_id = wsr.ID
AND aci.service_table = 'wor_service_request'
AND aci.delete_flag = '0' AND aci.delete_flag = '0'
WHERE wsr.delete_flag = '0' WHERE wsr.delete_flag = '0'
AND wsr.prescription_no = #{prescriptionNo} AND wsr.prescription_no = #{prescriptionNo}
@@ -67,6 +68,7 @@
<result column="anesthesia_type_enum" property="anesthesiaTypeEnum"/> <result column="anesthesia_type_enum" property="anesthesiaTypeEnum"/>
<result column="incision_level" property="incisionLevel"/> <result column="incision_level" property="incisionLevel"/>
<result column="surgery_level" property="surgeryLevel"/> <result column="surgery_level" property="surgeryLevel"/>
<result column="identifier_no" property="identifierNo"/>
</resultMap> </resultMap>
<!-- 分页查询申请单 --> <!-- 分页查询申请单 -->
@@ -91,14 +93,29 @@
cs.anesthesia_type_enum, cs.anesthesia_type_enum,
cs.incision_level, cs.incision_level,
cs.surgery_level, cs.surgery_level,
fc.contract_name AS fee_type fc.contract_name AS fee_type,
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifier_no
FROM doc_request_form drf FROM doc_request_form drf
LEFT JOIN cli_surgery cs ON cs.surgery_no = drf.prescription_no LEFT JOIN cli_surgery cs ON cs.surgery_no = drf.prescription_no
LEFT JOIN adm_patient ap ON ap.id = cs.patient_id LEFT JOIN adm_patient ap ON ap.id = cs.patient_id
LEFT JOIN adm_encounter ae ON ae.id = cs.encounter_id LEFT JOIN adm_encounter ae ON ae.id = cs.encounter_id
LEFT JOIN adm_account aa ON aa.encounter_id = ae.id AND aa.delete_flag = '0' LEFT JOIN adm_account aa ON aa.encounter_id = ae.id AND aa.delete_flag = '0'
LEFT JOIN fin_contract fc ON fc.bus_no = aa.contract_no AND fc.delete_flag = '0' LEFT JOIN fin_contract fc ON fc.bus_no = aa.contract_no AND fc.delete_flag = '0'
LEFT JOIN op_schedule os ON os.apply_id = drf.id AND os.delete_flag = '0'
LEFT JOIN (
SELECT patient_id, identifier_no
FROM (
SELECT patient_id, identifier_no,
ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY create_time ASC) AS rn
FROM adm_patient_identifier
WHERE delete_flag = '0' AND identifier_no IS NOT NULL AND identifier_no != ''
) t
WHERE rn = 1
) pi ON ap.id = pi.patient_id
<where> <where>
<if test="requestFormDto.surgeryNo != null and requestFormDto.surgeryNo != ''">
AND drf.prescription_no LIKE CONCAT('%', #{requestFormDto.surgeryNo}, '%')
</if>
<if test="requestFormDto.applyTimeStart != null"> <if test="requestFormDto.applyTimeStart != null">
AND drf.create_time >= #{requestFormDto.applyTimeStart} AND drf.create_time >= #{requestFormDto.applyTimeStart}
</if> </if>
@@ -112,6 +129,7 @@
AND cs.apply_dept_id = #{requestFormDto.applyDeptId} AND cs.apply_dept_id = #{requestFormDto.applyDeptId}
</if> </if>
AND drf.delete_flag = '0' AND drf.delete_flag = '0'
AND os.schedule_id IS NULL
</where> </where>
ORDER BY drf.create_time DESC ORDER BY drf.create_time DESC
</select> </select>

View File

@@ -769,15 +769,21 @@ public class CommonConstants {
} }
/** /**
* 号源槽位状态 (adm_schedule_slot.slot_status) * 号源槽位状态 (adm_schedule_slot.status)
*/ */
public interface SlotStatus { public interface SlotStatus {
/** 可用 / 待预约 */ /** 可用 / 待预约 */
Integer AVAILABLE = 0; Integer AVAILABLE = 0;
/** 已预约 */ /** 已预约 */
Integer BOOKED = 1; Integer BOOKED = 1;
/** 已停诊 / 已失效 */ /** 已取消 / 已停诊 */
Integer STOPPED = 2; Integer CANCELLED = 2;
/** 已锁定 */
Integer LOCKED = 3;
/** 已签到 / 已取号 */
Integer CHECKED_IN = 4;
/** 已退号 */
Integer RETURNED = 5;
} }
/** /**
@@ -790,6 +796,8 @@ public class CommonConstants {
Integer CHECKED_IN = 2; Integer CHECKED_IN = 2;
/** 已取消 */ /** 已取消 */
Integer CANCELLED = 3; Integer CANCELLED = 3;
/** 已退号 */
Integer RETURNED = 4;
} }
} }

View File

@@ -31,10 +31,20 @@ public enum ChargeItemContext implements HisEnumInterface {
*/ */
ACTIVITY(3, "3", "项目"), ACTIVITY(3, "3", "项目"),
/**
* 西药
*/
WESTERN_MEDICINE(5, "5", "西药"),
/**
* 中成药
*/
CHINESE_PATENT_MEDICINE(6, "6", "中成药"),
/** /**
* 挂号 * 挂号
*/ */
REGISTER(4, "4", "挂号"); REGISTER(7, "7", "挂号");
private final Integer value; private final Integer value;
private final String code; private final String code;

View File

@@ -86,7 +86,12 @@ public enum DispenseStatus implements HisEnumInterface {
/** /**
* 待退药 * 待退药
*/ */
PENDING_REFUND(16, "PRD", "待退药"); PENDING_REFUND(16, "PRD", "待退药"),
/**
* 已退药
*/
RETURNED(17, "RT", "已退药");
private Integer value; private Integer value;
private String code; private String code;

View File

@@ -23,14 +23,19 @@ public enum ItemType implements HisEnumInterface {
MEDICINE(1, "1", "药品"), MEDICINE(1, "1", "药品"),
/** /**
* 耗材 * 医疗活动
*/
ACTIVITY(3, "3", "医疗活动"),
/**
* 耗材前端使用值2
*/ */
DEVICE(2, "2", "耗材"), DEVICE(2, "2", "耗材"),
/** /**
* 医疗活动 * 手术前端使用值6避免与耗材冲突
*/ */
ACTIVITY(3, "3", "医疗活动"); SURGERY(6, "6", "手术");
@EnumValue @EnumValue
private Integer value; private Integer value;

View File

@@ -9,6 +9,8 @@ import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import java.time.LocalTime; import java.time.LocalTime;
import java.util.Date;
/** /**
* 号源池明细Entity * 号源池明细Entity
* *
@@ -29,7 +31,7 @@ public class ScheduleSlot extends HisBaseEntity {
/** 序号 */ /** 序号 */
private Integer seqNo; private Integer seqNo;
/** 序号状态: 0-可用,1-已预约,2-已取消,3-已过期等 */ /** 序号状态: 0-可用,1-已预约,2-已取消/已停诊,3-已锁定,4-已签到,5-已退号 */
private Integer status; private Integer status;
/** 预约订单ID */ /** 预约订单ID */
@@ -37,4 +39,7 @@ public class ScheduleSlot extends HisBaseEntity {
/** 预计叫号时间 */ /** 预计叫号时间 */
private LocalTime expectTime; private LocalTime expectTime;
/** 签到时间 */
private Date checkInTime;
} }

View File

@@ -11,6 +11,7 @@ import java.time.LocalTime;
public class TicketSlotDTO { public class TicketSlotDTO {
// 基础信息 // 基础信息
private Long slotId; private Long slotId;
private Integer seqNo;
private Long scheduleId; private Long scheduleId;
private String doctor; private String doctor;
private Long doctorId; private Long doctorId;
@@ -22,6 +23,13 @@ public class TicketSlotDTO {
private Long patientId; private Long patientId;
private String phone; private String phone;
private Integer orderStatus; private Integer orderStatus;
private Long orderId;
private String orderNo;
private String patientGender;
private Integer genderEnum;
private String idCard;
private String encounterId;
private String appointmentTime;
// 底层逻辑判断专属字段 // 底层逻辑判断专属字段
private Integer slotStatus; private Integer slotStatus;

View File

@@ -37,4 +37,21 @@ public interface SchedulePoolMapper extends BaseMapper<SchedulePool> {
AND p.delete_flag = '0' AND p.delete_flag = '0'
""") """)
int refreshPoolStats(@Param("poolId") Long poolId); int refreshPoolStats(@Param("poolId") Long poolId);
/**
* 签到时更新号源池统计:锁定数-1已预约数+1
*
* @param poolId 号源池ID
* @return 结果
*/
@Update("""
UPDATE adm_schedule_pool
SET locked_num = locked_num - 1,
booked_num = booked_num + 1,
update_time = NOW()
WHERE id = #{poolId}
AND locked_num > 0
AND delete_flag = '0'
""")
int updatePoolStatsOnCheckIn(@Param("poolId") Integer poolId);
} }

View File

@@ -6,6 +6,7 @@ import com.openhis.appointmentmanage.domain.ScheduleSlot;
import com.openhis.appointmentmanage.domain.TicketSlotDTO; import com.openhis.appointmentmanage.domain.TicketSlotDTO;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.openhis.appointmentmanage.dto.TicketQueryDTO; import com.openhis.appointmentmanage.dto.TicketQueryDTO;
import java.util.Date;
import java.util.List; import java.util.List;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@@ -30,6 +31,16 @@ public interface ScheduleSlotMapper extends BaseMapper<ScheduleSlot> {
*/ */
int updateSlotStatus(@Param("slotId") Long slotId, @Param("status") Integer status); int updateSlotStatus(@Param("slotId") Long slotId, @Param("status") Integer status);
/**
* 更新槽位状态并记录签到时间
*
* @param slotId 槽位ID
* @param status 状态
* @param checkInTime 签到时间
* @return 结果
*/
int updateSlotStatusAndCheckInTime(@Param("slotId") Long slotId, @Param("status") Integer status, @Param("checkInTime") Date checkInTime);
/** /**
* 根据槽位ID查询所属号源池ID。 * 根据槽位ID查询所属号源池ID。
*/ */

View File

@@ -20,7 +20,7 @@ import lombok.experimental.Accessors;
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
public class Order extends HisBaseEntity { public class Order extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID) @TableId(type = IdType.AUTO)
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private Long id; private Long id;

View File

@@ -32,4 +32,14 @@ public interface OrderMapper extends BaseMapper<Order> {
int updateOrderStatusById(Long id, Integer status); int updateOrderStatusById(Long id, Integer status);
int updateOrderCancelInfoById(Long id, Date cancelTime, String cancelReason); int updateOrderCancelInfoById(Long id, Date cancelTime, String cancelReason);
/**
* 更新订单支付状态
*
* @param orderId 订单ID
* @param payStatus 支付状态0-未支付1-已支付
* @param payTime 支付时间
* @return 结果
*/
int updatePayStatus(@Param("orderId") Long orderId, @Param("payStatus") Integer payStatus, @Param("payTime") Date payTime);
} }

View File

@@ -88,6 +88,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
} }
Order order = new Order(); Order order = new Order();
order.setId(null); // 显式置空,确保触发数据库自增,避免 MP 预分配雪花 ID 的干扰
String orderNo = assignSeqUtil.getSeq(AssignSeqEnum.ORDER_NUM.getPrefix(), 18); String orderNo = assignSeqUtil.getSeq(AssignSeqEnum.ORDER_NUM.getPrefix(), 18);
order.setOrderNo(orderNo); order.setOrderNo(orderNo);

View File

@@ -5,6 +5,7 @@ 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.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;
import com.openhis.clinical.domain.Order; import com.openhis.clinical.domain.Order;
@@ -52,6 +53,9 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
@Resource @Resource
private SchedulePoolMapper schedulePoolMapper; private SchedulePoolMapper schedulePoolMapper;
@Resource
private com.openhis.clinical.mapper.OrderMapper orderMapper;
@Resource @Resource
private IAppointmentConfigService appointmentConfigService; private IAppointmentConfigService appointmentConfigService;
@@ -146,7 +150,25 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
logger.debug("开始执行纯净打单路线slotId: {}, patientName: {}", slotId, dto.getPatientName()); logger.debug("开始执行纯净打单路线slotId: {}, patientName: {}", slotId, dto.getPatientName());
// 1. 直查物理大底座! // 1. 检查患者取消预约次数限制(应在预约挂号时限制,而非取消预约时)
Integer tenantId = dto.getTenant_id();
Long patientId = dto.getPatientId();
if (tenantId != null && patientId != null) {
AppointmentConfig config = appointmentConfigService.getConfigByTenantId(tenantId);
if (config != null && config.getCancelAppointmentCount() != null
&& config.getCancelAppointmentCount() > 0) {
// 计算当前周期的起始时间
LocalDateTime startTime = calculatePeriodStartTime(config.getCancelAppointmentType());
// 统计已取消次数
long cancelledCount = orderService.countPatientCancellations(patientId, tenantId, startTime);
if (cancelledCount >= config.getCancelAppointmentCount()) {
String periodName = getPeriodName(config.getCancelAppointmentType());
throw new RuntimeException("由于您在" + periodName + "内累计取消预约已达" + cancelledCount + "次,触发系统限制,暂时无法在线预约,请联系分诊台或咨询客服。");
}
}
}
// 2. 直查物理大底座!
TicketSlotDTO slot = scheduleSlotMapper.selectTicketSlotById(slotId); TicketSlotDTO slot = scheduleSlotMapper.selectTicketSlotById(slotId);
if (slot == null) { if (slot == null) {
@@ -242,26 +264,11 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
throw new RuntimeException("当前号源没有可取消的预约订单"); throw new RuntimeException("当前号源没有可取消的预约订单");
} }
// 核心逻辑:获取订单信息并检查机构取消限制 // 获取订单信息
Order latestOrder = orders.get(0); Order latestOrder = orders.get(0);
Integer tenantId = latestOrder.getTenantId();
Long patientId = latestOrder.getPatientId();
if (tenantId != null && patientId != null) {
AppointmentConfig config = appointmentConfigService.getConfigByTenantId(tenantId);
if (config != null && config.getCancelAppointmentCount() != null
&& config.getCancelAppointmentCount() > 0) {
// 计算当前周期的起始时间
LocalDateTime startTime = calculatePeriodStartTime(config.getCancelAppointmentType());
// 统计已取消次数
long cancelledCount = orderService.countPatientCancellations(patientId, tenantId, startTime);
if (cancelledCount >= config.getCancelAppointmentCount()) {
String periodName = getPeriodName(config.getCancelAppointmentType());
throw new RuntimeException("您在" + periodName + "内已达到该机构取消预约次数上限(" + config.getCancelAppointmentCount() + "次),禁止取消");
}
}
}
// 直接执行取消,不再检查取消限制
// 根据需求,取消限制应在预约挂号时检查,而非取消预约时
for (Order order : orders) { for (Order order : orders) {
orderService.cancelAppointmentOrder(order.getId(), "患者取消预约"); orderService.cancelAppointmentOrder(order.getId(), "患者取消预约");
} }
@@ -274,7 +281,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
} }
/** /**
* 取号 * 取号(签到)
* *
* @param slotId 槽位ID * @param slotId 槽位ID
* @return 结果 * @return 结果
@@ -287,7 +294,24 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
throw new RuntimeException("当前号源没有可取号的预约订单"); throw new RuntimeException("当前号源没有可取号的预约订单");
} }
Order latestOrder = orders.get(0); Order latestOrder = orders.get(0);
return orderService.updateOrderStatusById(latestOrder.getId(), AppointmentOrderStatus.CHECKED_IN);
// 1. 更新订单状态为已取号,并更新支付状态和支付时间
orderService.updateOrderStatusById(latestOrder.getId(), AppointmentOrderStatus.CHECKED_IN);
// 更新支付状态为已支付,记录支付时间
orderMapper.updatePayStatus(latestOrder.getId(), 1, new Date());
// 2. 查询号源槽位信息
ScheduleSlot slot = scheduleSlotMapper.selectById(slotId);
// 3. 更新号源槽位状态为已签到,记录签到时间
scheduleSlotMapper.updateSlotStatusAndCheckInTime(slotId, SlotStatus.CHECKED_IN, new Date());
// 4. 更新号源池统计:锁定数-1已预约数+1
if (slot != null && slot.getPoolId() != null) {
schedulePoolMapper.updatePoolStatsOnCheckIn(slot.getPoolId());
}
return 1;
} }
/** /**
@@ -309,7 +333,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
orderService.cancelAppointmentOrder(order.getId(), "医生停诊"); orderService.cancelAppointmentOrder(order.getId(), "医生停诊");
} }
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.STOPPED); int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.CANCELLED);
if (updated > 0) { if (updated > 0) {
refreshPoolStatsBySlotId(slotId); refreshPoolStatsBySlotId(slotId);
} }

View File

@@ -17,7 +17,7 @@ import java.util.Date;
* @date 2025-06-03 * @date 2025-06-03
*/ */
@Data @Data
@TableName("doc_ital_signs") @TableName("doc_vital_signs")
@Accessors(chain = true) @Accessors(chain = true)
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
public class VitalSigns extends HisBaseEntity { public class VitalSigns extends HisBaseEntity {

View File

@@ -46,16 +46,24 @@ public class PaymentRecStaticServiceImpl extends ServiceImpl<PaymentRecStaticMap
Map<String, List<ChargeItemDefInfo>> collect; Map<String, List<ChargeItemDefInfo>> collect;
List<PaymentRecStatic> paymentRecStatics = new ArrayList<>(); List<PaymentRecStatic> paymentRecStatics = new ArrayList<>();
if (paymentStatisticalMethod == PaymentStatisticalMethod.FIN_TYPE_CODE) { if (paymentStatisticalMethod == PaymentStatisticalMethod.FIN_TYPE_CODE) {
collect = chargeItemDefInfoList.stream().collect(Collectors.groupingBy(ChargeItemDefInfo::getTypeCode)); collect = chargeItemDefInfoList.stream()
.filter(info -> info.getTypeCode() != null)
.collect(Collectors.groupingBy(ChargeItemDefInfo::getTypeCode));
getPaymentRecStaticList(paymentId, paymentType, paymentStatisticalMethod, collect, paymentRecStatics); getPaymentRecStaticList(paymentId, paymentType, paymentStatisticalMethod, collect, paymentRecStatics);
} else if (paymentStatisticalMethod == PaymentStatisticalMethod.MED_CHRGITM_TYPE) { } else if (paymentStatisticalMethod == PaymentStatisticalMethod.MED_CHRGITM_TYPE) {
collect = chargeItemDefInfoList.stream().collect(Collectors.groupingBy(ChargeItemDefInfo::getYbType)); collect = chargeItemDefInfoList.stream()
.filter(info -> info.getYbType() != null)
.collect(Collectors.groupingBy(ChargeItemDefInfo::getYbType));
getPaymentRecStaticList(paymentId, paymentType, paymentStatisticalMethod, collect, paymentRecStatics); getPaymentRecStaticList(paymentId, paymentType, paymentStatisticalMethod, collect, paymentRecStatics);
} else { } else {
collect = chargeItemDefInfoList.stream().collect(Collectors.groupingBy(ChargeItemDefInfo::getTypeCode)); collect = chargeItemDefInfoList.stream()
.filter(info -> info.getTypeCode() != null)
.collect(Collectors.groupingBy(ChargeItemDefInfo::getTypeCode));
getPaymentRecStaticList(paymentId, paymentType, PaymentStatisticalMethod.FIN_TYPE_CODE, collect, getPaymentRecStaticList(paymentId, paymentType, PaymentStatisticalMethod.FIN_TYPE_CODE, collect,
paymentRecStatics); paymentRecStatics);
collect = chargeItemDefInfoList.stream().collect(Collectors.groupingBy(ChargeItemDefInfo::getYbType)); collect = chargeItemDefInfoList.stream()
.filter(info -> info.getYbType() != null)
.collect(Collectors.groupingBy(ChargeItemDefInfo::getYbType));
getPaymentRecStaticList(paymentId, paymentType, PaymentStatisticalMethod.MED_CHRGITM_TYPE, collect, getPaymentRecStaticList(paymentId, paymentType, PaymentStatisticalMethod.MED_CHRGITM_TYPE, collect,
paymentRecStatics); paymentRecStatics);
} }

View File

@@ -24,9 +24,14 @@ import java.util.Date;
public class InspectionLabApply extends HisBaseEntity { public class InspectionLabApply extends HisBaseEntity {
/** /**
* 主键ID,申请单编号 * 主键ID(自增)
*/
@TableId(type = IdType.AUTO)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/**
* 申请单编号
*/ */
@TableId(type = IdType.NONE)
private String applyNo; private String applyNo;
/** /**
* 患者主索引 * 患者主索引

View File

@@ -55,7 +55,10 @@ public class InspectionPackageDetail {
/** 单位 */ /** 单位 */
private String unit; private String unit;
/** 单价 */ /** 原始单价(未折扣前的价格,用于折扣变更时恢复原价) */
private BigDecimal originalPrice;
/** 单价(折后单价 = 原始单价 × 折扣比例) */
private BigDecimal unitPrice; private BigDecimal unitPrice;
/** 金额 */ /** 金额 */

View File

@@ -0,0 +1,106 @@
package com.openhis.lab.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 检验项目定义实体(独立表 lab_activity_definition不依赖 wor_activity_definition
*/
@Data
@TableName("lab_activity_definition")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class LabActivityDefinition extends HisBaseEntity {
/** 主键ID */
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/** 目录类别 */
private String categoryCode;
/** 编码 */
private String busNo;
/** 项目名称 */
private String name;
/** 项目名称拼音 */
private String pyStr;
/** 五笔拼音 */
private String wbStr;
/** 类型 */
private Integer typeEnum;
/** 使用单位 */
private String permittedUnitCode;
/** 所属科室 */
@JsonSerialize(using = ToStringSerializer.class)
private Long orgId;
/** 所在位置 */
@JsonSerialize(using = ToStringSerializer.class)
private Long locationId;
/** 医保标记 */
private Integer ybFlag;
/** 医保编码 */
private String ybNo;
/** 医保对码标记 */
private Integer ybMatchFlag;
/** 状态 */
private Integer statusEnum;
/** 身体部位 */
private String bodySiteCode;
/** 所需标本 */
private String specimenCode;
/** 说明 */
private String descriptionText;
/** 规则id */
private Integer ruleId;
/** 医保等级 */
private Integer chrgitmLv;
/** 子项json */
private String childrenJson;
/** 划价标记 */
private Integer pricingFlag;
/** 序号 */
private Integer sortOrder;
/** 服务范围 */
private String serviceRange;
/** 检验类型ID关联 inspection_type 大类,逻辑关联无外键) */
@JsonSerialize(using = ToStringSerializer.class)
private Long inspectionTypeId;
/** 费用套餐ID关联 inspection_basic_information逻辑关联无外键 */
@JsonSerialize(using = ToStringSerializer.class)
private Long feePackageId;
/** 下级医技类型ID关联 inspection_type 子类,逻辑关联无外键) */
@JsonSerialize(using = ToStringSerializer.class)
private Long subItemId;
}

View File

@@ -0,0 +1,13 @@
package com.openhis.lab.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.openhis.lab.domain.LabActivityDefinition;
import org.springframework.stereotype.Repository;
/**
* 检验项目定义 Mapper 接口
*/
@Repository
public interface LabActivityDefinitionMapper extends BaseMapper<LabActivityDefinition> {
}

View File

@@ -0,0 +1,18 @@
package com.openhis.lab.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.openhis.lab.domain.LabActivityDefinition;
/**
* 检验项目定义 Service 接口
*/
public interface ILabActivityDefinitionService extends IService<LabActivityDefinition> {
/**
* 新增检验项目
*
* @param labActivityDefinition 检验项目实体
* @return 是否成功
*/
boolean addLabActivityDefinition(LabActivityDefinition labActivityDefinition);
}

View File

@@ -0,0 +1,63 @@
package com.openhis.lab.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.core.common.utils.SecurityUtils;
import com.core.common.core.domain.model.LoginUser;
import com.openhis.lab.domain.LabActivityDefinition;
import com.openhis.lab.mapper.LabActivityDefinitionMapper;
import com.openhis.lab.service.ILabActivityDefinitionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
/**
* 检验项目定义 Service 实现类
*/
@Slf4j
@Service
public class LabActivityDefinitionServiceImpl
extends ServiceImpl<LabActivityDefinitionMapper, LabActivityDefinition>
implements ILabActivityDefinitionService {
/**
* 新增检验项目(检查编码唯一性)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean addLabActivityDefinition(LabActivityDefinition labActivityDefinition) {
// 根据编码判断是否已存在
List<LabActivityDefinition> existing = baseMapper.selectList(
new LambdaQueryWrapper<LabActivityDefinition>()
.eq(LabActivityDefinition::getBusNo, labActivityDefinition.getBusNo()));
if (!existing.isEmpty()) {
return false;
}
setRequiredFields(labActivityDefinition);
return baseMapper.insert(labActivityDefinition) == 1;
}
/**
* 补全必填字段create_by、tenant_id、create_time
*/
private void setRequiredFields(LabActivityDefinition entity) {
String createBy = "system";
Integer tenantId = 1;
try {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser != null) {
createBy = loginUser.getUsername();
tenantId = loginUser.getTenantId() != null ? loginUser.getTenantId() : 1;
}
} catch (Exception ignored) {
}
entity.setCreateBy(createBy);
entity.setTenantId(tenantId);
if (entity.getCreateTime() == null) {
entity.setCreateTime(new Date());
}
}
}

View File

@@ -37,6 +37,10 @@ public class OpSchedule extends HisBaseEntity {
/** 就诊ID */ /** 就诊ID */
private Long visitId; private Long visitId;
/** 就诊卡号 */
@TableField(exist = false)
private String identifierNo;
/** 手术编码 */ /** 手术编码 */
private String operCode; private String operCode;

View File

@@ -4,10 +4,46 @@
"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 -->
<sql id="slotStatusNormExpr">
CASE
WHEN LOWER(CONCAT('', s.status)) IN ('0', 'unbooked', 'available') THEN 0
WHEN LOWER(CONCAT('', s.status)) IN ('1', 'booked') THEN 1
WHEN LOWER(CONCAT('', s.status)) IN ('2', 'cancelled', 'canceled', 'stopped') THEN 2
WHEN LOWER(CONCAT('', s.status)) IN ('3', 'locked') THEN 3
WHEN LOWER(CONCAT('', s.status)) IN ('4', 'checked', 'checked_in', 'checkin') THEN 4
WHEN LOWER(CONCAT('', s.status)) IN ('5', 'returned') THEN 5
ELSE NULL
END
</sql>
<sql id="orderStatusNormExpr">
CASE
WHEN LOWER(CONCAT('', o.status)) IN ('1', 'booked') THEN 1
WHEN LOWER(CONCAT('', o.status)) IN ('2', 'checked', 'checked_in', 'checkin') THEN 2
WHEN LOWER(CONCAT('', o.status)) IN ('3', 'cancelled', 'canceled') THEN 3
WHEN LOWER(CONCAT('', o.status)) IN ('4', 'returned') THEN 4
ELSE NULL
END
</sql>
<sql id="poolStatusNormExpr">
CASE
WHEN LOWER(CONCAT('', p.status)) IN ('0', 'unbooked', 'available') THEN 0
WHEN LOWER(CONCAT('', p.status)) IN ('1', 'booked') THEN 1
WHEN LOWER(CONCAT('', p.status)) IN ('2', 'cancelled', 'canceled', 'stopped') THEN 2
WHEN LOWER(CONCAT('', p.status)) IN ('3', 'locked') THEN 3
WHEN LOWER(CONCAT('', p.status)) IN ('4', 'checked', 'checked_in', 'checkin') THEN 4
WHEN LOWER(CONCAT('', p.status)) IN ('5', 'returned') THEN 5
ELSE NULL
END
</sql>
<!-- 注意这里的 resultType 指向了您刚建好的 DTO 实体类 --> <!-- 注意这里的 resultType 指向了您刚建好的 DTO 实体类 -->
<select id="selectAllTicketSlots" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO"> <select id="selectAllTicketSlots" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
SELECT SELECT
s.id AS slotId, s.id AS slotId,
s.seq_no AS seqNo,
p.schedule_id AS scheduleId, p.schedule_id AS scheduleId,
p.doctor_name AS doctor, p.doctor_name AS doctor,
p.dept_id AS departmentId, p.dept_id AS departmentId,
@@ -16,12 +52,12 @@
o.patient_name AS patientName, o.patient_name AS patientName,
o.medical_card AS medicalCard, o.medical_card AS medicalCard,
o.phone AS phone, o.phone AS phone,
o.status AS orderStatus, <include refid="orderStatusNormExpr" /> AS orderStatus,
s.status AS slotStatus, <include refid="slotStatusNormExpr" /> AS slotStatus,
s.expect_time AS expectTime, s.expect_time AS expectTime,
p.schedule_date AS scheduleDate, p.schedule_date AS scheduleDate,
d.reg_type AS regType, d.reg_type AS regType,
p.status AS poolStatus, <include refid="poolStatusNormExpr" /> AS poolStatus,
p.stop_reason AS stopReason, p.stop_reason AS stopReason,
d.is_stopped AS isStopped d.is_stopped AS isStopped
FROM FROM
@@ -39,7 +75,7 @@
FROM FROM
order_main order_main
WHERE WHERE
status IN (1, 2) LOWER(CONCAT('', status)) IN ('1', '2', '4', 'booked', 'checked', 'checked_in', 'checkin', 'returned')
ORDER BY ORDER BY
slot_id, slot_id,
create_time DESC create_time DESC
@@ -56,6 +92,7 @@
<select id="selectTicketSlotById" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO"> <select id="selectTicketSlotById" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
SELECT SELECT
s.id AS slotId, s.id AS slotId,
s.seq_no AS seqNo,
p.schedule_id AS scheduleId, p.schedule_id AS scheduleId,
p.doctor_name AS doctor, p.doctor_name AS doctor,
p.doctor_id AS doctorId, p.doctor_id AS doctorId,
@@ -66,12 +103,18 @@
o.patient_name AS patientName, o.patient_name AS patientName,
o.medical_card AS medicalCard, o.medical_card AS medicalCard,
o.phone AS phone, o.phone AS phone,
o.status AS orderStatus, o.id AS orderId,
s.status AS slotStatus, o.order_no AS orderNo,
COALESCE(CAST(o.gender AS VARCHAR), CAST(pinfo.gender_enum AS VARCHAR)) AS patientGender,
pinfo.gender_enum AS genderEnum,
pinfo.id_card AS idCard,
o.appointment_time AS appointmentTime,
<include refid="orderStatusNormExpr" /> AS orderStatus,
<include refid="slotStatusNormExpr" /> AS slotStatus,
s.expect_time AS expectTime, s.expect_time AS expectTime,
p.schedule_date AS scheduleDate, p.schedule_date AS scheduleDate,
d.reg_type AS regType, d.reg_type AS regType,
p.status AS poolStatus, <include refid="poolStatusNormExpr" /> AS poolStatus,
p.stop_reason AS stopReason, p.stop_reason AS stopReason,
d.is_stopped AS isStopped d.is_stopped AS isStopped
FROM FROM
@@ -87,15 +130,20 @@
patient_name, patient_name,
medical_card, medical_card,
phone, phone,
id,
order_no,
gender,
appointment_time,
status status
FROM FROM
order_main order_main
WHERE WHERE
status IN (1, 2) LOWER(CONCAT('', status)) IN ('1', '2', '4', 'booked', 'checked', 'checked_in', 'checkin', 'returned')
ORDER BY ORDER BY
slot_id, slot_id,
create_time DESC create_time DESC
) o ON o.slot_id = s.id ) o ON o.slot_id = s.id
LEFT JOIN adm_patient pinfo ON o.patient_id = pinfo.id
WHERE WHERE
s.id = #{id} s.id = #{id}
</select> </select>
@@ -115,7 +163,7 @@
UPDATE adm_schedule_slot UPDATE adm_schedule_slot
SET SET
status = #{status}, status = #{status},
<if test="status == 0"> <if test="status != null and '0'.equals(status.toString())">
order_id = NULL, order_id = NULL,
</if> </if>
update_time = now() update_time = now()
@@ -124,10 +172,24 @@
AND delete_flag = '0' AND delete_flag = '0'
</update> </update>
<update id="updateSlotStatusAndCheckInTime">
UPDATE adm_schedule_slot
SET
status = #{status},
check_in_time = #{checkInTime},
update_time = NOW()
WHERE
id = #{slotId}
AND delete_flag = '0'
</update>
<select id="selectPoolIdBySlotId" resultType="java.lang.Long"> <select id="selectPoolIdBySlotId" resultType="java.lang.Long">
SELECT pool_id SELECT
FROM adm_schedule_slot pool_id
WHERE id = #{slotId} FROM
adm_schedule_slot
WHERE
id = #{slotId}
AND delete_flag = '0' AND delete_flag = '0'
</select> </select>
@@ -145,6 +207,7 @@
<select id="selectTicketSlotsPage" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO"> <select id="selectTicketSlotsPage" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
SELECT SELECT
s.id AS slotId, s.id AS slotId,
s.seq_no AS seqNo,
p.schedule_id AS scheduleId, p.schedule_id AS scheduleId,
p.doctor_name AS doctor, p.doctor_name AS doctor,
p.doctor_id AS doctorId, p.doctor_id AS doctorId,
@@ -155,12 +218,18 @@
o.patient_name AS patientName, o.patient_name AS patientName,
o.medical_card AS medicalCard, o.medical_card AS medicalCard,
o.phone AS phone, o.phone AS phone,
o.status AS orderStatus, o.id AS orderId,
s.status AS slotStatus, o.order_no AS orderNo,
COALESCE(CAST(o.gender AS VARCHAR), CAST(pinfo.gender_enum AS VARCHAR)) AS patientGender,
pinfo.gender_enum AS genderEnum,
pinfo.id_card AS idCard,
o.appointment_time AS appointmentTime,
<include refid="orderStatusNormExpr" /> AS orderStatus,
<include refid="slotStatusNormExpr" /> AS slotStatus,
s.expect_time AS expectTime, s.expect_time AS expectTime,
p.schedule_date AS scheduleDate, p.schedule_date AS scheduleDate,
d.reg_type AS regType, d.reg_type AS regType,
p.status AS poolStatus, <include refid="poolStatusNormExpr" /> AS poolStatus,
p.stop_reason AS stopReason, p.stop_reason AS stopReason,
d.is_stopped AS isStopped d.is_stopped AS isStopped
FROM FROM
@@ -176,15 +245,20 @@
patient_name, patient_name,
medical_card, medical_card,
phone, phone,
id,
order_no,
gender,
appointment_time,
status status
FROM FROM
order_main order_main
WHERE WHERE
status IN (1, 2) LOWER(CONCAT('', status)) IN ('1', '2', '4', 'booked', 'checked', 'checked_in', 'checkin', 'returned')
ORDER BY ORDER BY
slot_id, slot_id,
create_time DESC create_time DESC
) o ON o.slot_id = s.id ) o ON o.slot_id = s.id
LEFT JOIN adm_patient pinfo ON o.patient_id = pinfo.id
<where> <where>
p.delete_flag = '0' p.delete_flag = '0'
AND s.delete_flag = '0' <!-- 1. 按日期查 --> AND s.delete_flag = '0' <!-- 1. 按日期查 -->
@@ -225,35 +299,49 @@
<!-- 5. 核心:解答您疑问的 4 种业务状态的复合查询! --> <!-- 5. 核心:解答您疑问的 4 种业务状态的复合查询! -->
<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="query.status == 'unbooked'"> <when test="'unbooked'.equals(query.status) or '未预约'.equals(query.status)">
AND s.status = 0 AND <include refid="slotStatusNormExpr" /> = 0
AND ( AND (
d.is_stopped IS NULL d.is_stopped IS NULL
OR d.is_stopped = FALSE OR d.is_stopped = FALSE
) )
</when> </when>
<when test="query.status == 'booked'"> <when test="'booked'.equals(query.status) or '已预约'.equals(query.status)">
AND s.status = 1 AND <include refid="slotStatusNormExpr" /> = 1
AND o.status = 1 AND <include refid="orderStatusNormExpr" /> = 1
AND ( AND (
d.is_stopped IS NULL d.is_stopped IS NULL
OR d.is_stopped = FALSE OR d.is_stopped = FALSE
) )
</when> </when>
<when test="query.status == 'checked'"> <when test="'checked'.equals(query.status) or '已取号'.equals(query.status)">
AND s.status = 1 AND (
AND o.status = 2 <include refid="slotStatusNormExpr" /> = 4
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
) )
</when> </when>
<when test="query.status == 'cancelled'"> <when test="'cancelled'.equals(query.status) or '已停诊'.equals(query.status) or '已取消'.equals(query.status)">
AND ( AND (
s.status = 2 <include refid="slotStatusNormExpr" /> = 2
OR d.is_stopped = TRUE OR d.is_stopped = TRUE
) )
</when> </when>
<when test="'returned'.equals(query.status) or '已退号'.equals(query.status)">
AND (
<include refid="slotStatusNormExpr" /> = 5
OR <include refid="orderStatusNormExpr" /> = 4
)
</when>
<otherwise>
AND 1 = 2
</otherwise>
</choose> </choose>
</if> </if>
</where> </where>
@@ -266,9 +354,22 @@
SELECT SELECT
p.doctor_id AS doctorId, p.doctor_id AS doctorId,
p.doctor_name AS doctorName, p.doctor_name AS doctorName,
COALESCE(SUM(GREATEST(COALESCE(p.total_quota, 0) - COALESCE(p.booked_num, 0) - COALESCE(p.locked_num, 0), 0)), 0) AS available, COALESCE(
SUM(
GREATEST(
COALESCE(p.total_quota, 0) - COALESCE(p.booked_num, 0) - COALESCE(p.locked_num, 0),
0
)
),
0
) AS available,
CASE CASE
WHEN MAX(CASE WHEN d.reg_type = 1 THEN 1 ELSE 0 END) = 1 THEN 'expert' WHEN MAX(
CASE
WHEN d.reg_type = 1 THEN 1
ELSE 0
END
) = 1 THEN 'expert'
ELSE 'general' ELSE 'general'
END AS ticketType END AS ticketType
FROM FROM
@@ -290,7 +391,10 @@
AND d.reg_type = 1 AND d.reg_type = 1
</when> </when>
<when test="query.type == 'general'"> <when test="query.type == 'general'">
AND (d.reg_type != 1 OR d.reg_type IS NULL) AND (
d.reg_type != 1
OR d.reg_type IS NULL
)
</when> </when>
</choose> </choose>
</if> </if>

View File

@@ -127,7 +127,7 @@
select * from order_main where slot_id = #{slotId} and status = 1 select * from order_main where slot_id = #{slotId} and status = 1
</select> </select>
<insert id="insertOrder" parameterType="com.openhis.clinical.domain.Order"> <insert id="insertOrder" parameterType="com.openhis.clinical.domain.Order" useGeneratedKeys="true" keyProperty="id">
insert into order_main insert into order_main
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">
<if test="orderNo != null and orderNo != ''">order_no,</if> <if test="orderNo != null and orderNo != ''">order_no,</if>
@@ -225,6 +225,12 @@
update order_main set status = 3, cancel_time = #{cancelTime}, cancel_reason = #{cancelReason} where id = #{id} update order_main set status = 3, cancel_time = #{cancelTime}, cancel_reason = #{cancelReason} where id = #{id}
</update> </update>
<update id="updatePayStatus">
update order_main
set pay_status = #{payStatus}, pay_time = #{payTime}, update_time = NOW()
where id = #{orderId}
</update>
<delete id="deleteOrderById"> <delete id="deleteOrderById">
delete from order_main where id = #{id} delete from order_main where id = #{id}
</delete> </delete>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.openhis.lab.mapper.LabActivityDefinitionMapper">
</mapper>

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,8 @@
---
title: HIS使用说明书
date: 2026-03-25 17:41:12
permalink: /pages/48821b/
---

View File

@@ -0,0 +1,91 @@
/**
* Emergency patch for lodash.template@4.18.x (broken assignWith).
* When package.json "overrides" pins lodash.template@4.5.0, do NOT run this — it is unnecessary and may corrupt the file.
*
* Usage: node fix-lodash-manual.js
*/
const fs = require('fs');
const path = require('path');
const lodashTemplatePkg = path.join(__dirname, 'node_modules', 'lodash.template', 'package.json');
if (fs.existsSync(lodashTemplatePkg)) {
try {
const ver = JSON.parse(fs.readFileSync(lodashTemplatePkg, 'utf8')).version || '';
if (ver.startsWith('4.5.')) {
console.log('✓ lodash.template is already 4.5.x (overrides); skip manual patch');
process.exit(0);
}
} catch (e) {
/* continue to patch path */
}
}
const lodashTemplatePath = path.join(__dirname, 'node_modules', 'lodash.template', 'index.js');
if (!fs.existsSync(lodashTemplatePath)) {
console.error('❌ lodash.template not found at:', lodashTemplatePath);
console.error('Please run npm install or yarn install first');
process.exit(1);
}
let content = fs.readFileSync(lodashTemplatePath, 'utf8');
// Check if already patched with new version
if (content.includes('/* LODASH_TEMPLATE_PATCHED_V2 */')) {
console.log('✓ lodash.template already patched with v2');
process.exit(0);
}
// Remove old patch if exists
if (content.includes('/* LODASH_TEMPLATE_PATCHED */')) {
console.log('🔄 Removing old patch...');
content = content.replace(/\/\* LODASH_TEMPLATE_PATCHED \*\/\n.*assignWith[\s\S]*?^\}/m, '');
}
console.log('🔧 Patching lodash.template with v2...');
// Correct assignWith implementation that handles undefined customizer
const assignWithImpl = `/* LODASH_TEMPLATE_PATCHED_V2 */
// assignWith polyfill for lodash.template - Fixed version
function assignWith(object, source, customizer) {
if (object == null) return object;
if (typeof customizer !== 'function') {
customizer = function(objValue, srcValue) {
return srcValue;
};
}
var props = Object.keys(Object(source));
for (var i = 0; i < props.length; i++) {
var key = props[i];
var value = source[key];
var assignedValue = customizer(object[key], value, key, object, source);
if (assignedValue !== undefined) {
object[key] = assignedValue;
}
}
return object;
}
`;
// Find first line break after comments
let insertPos = 0;
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
if (lines[i].trim() === ' */' || lines[i].trim() === '*/') {
insertPos = content.indexOf('\n', content.indexOf(lines[i])) + 1;
break;
}
}
if (insertPos === 0) {
insertPos = content.indexOf('\n') + 1;
}
const before = content.substring(0, insertPos);
const after = content.substring(insertPos);
content = before + '\n' + assignWithImpl + after;
fs.writeFileSync(lodashTemplatePath, content);
console.log('✅ Successfully patched lodash.template with assignWith function (v2)');
console.log(' File:', lodashTemplatePath);

View File

@@ -0,0 +1,55 @@
/**
* Fix lodash.template assignWith issue
* This script patches the lodash.template module to include assignWith
*/
const fs = require('fs');
const path = require('path');
const lodashTemplatePath = path.join(__dirname, 'node_modules', 'lodash.template', 'index.js');
if (!fs.existsSync(lodashTemplatePath)) {
console.log('lodash.template not found, skipping patch');
process.exit(0);
}
let content = fs.readFileSync(lodashTemplatePath, 'utf8');
// Check if already patched
if (content.includes('/* LODASH_TEMPLATE_PATCHED */')) {
console.log('lodash.template already patched');
process.exit(0);
}
// Simple assignWith implementation
const assignWithImpl = `
/* LODASH_TEMPLATE_PATCHED */
// Simple assignWith implementation
function assignWith(object, source, customizer) {
if (object == null) {
return object;
}
var props = Object.keys(Object(source));
var index = -1;
var length = props.length;
while (++index < length) {
var key = props[index];
var value = source[key];
var assignedValue = customizer ? customizer(object[key], value, key, object, source) : value;
if (assignedValue !== undefined) {
object[key] = assignedValue;
}
}
return object;
}
`;
// Insert at the beginning of the file after any comments
const firstLineEnd = content.indexOf('\n') + 1;
const before = content.substring(0, firstLineEnd);
const after = content.substring(firstLineEnd);
content = before + assignWithImpl + after;
fs.writeFileSync(lodashTemplatePath, content);
console.log('✓ Patched lodash.template with assignWith function');

View File

@@ -0,0 +1,64 @@
#!/bin/bash
# Emergency patch for lodash.template@4.18.x only. Skip when overrides pin 4.5.0.
# Run after npm install on Linux/Mac if needed.
if [ -f "node_modules/lodash.template/package.json" ]; then
VER=$(node -p "require('./node_modules/lodash.template/package.json').version" 2>/dev/null || echo "")
case "$VER" in
4.5.*) echo "✓ lodash.template is $VER; skip manual patch"; exit 0 ;;
esac
fi
LODGASH_TEMPLATE="node_modules/lodash.template/index.js"
if [ ! -f "$LODGASH_TEMPLATE" ]; then
echo "❌ lodash.template not found. Please run npm install or yarn install first."
exit 1
fi
# Check if already patched with v2
if grep -q "LODASH_TEMPLATE_PATCHED_V2" "$LODGASH_TEMPLATE"; then
echo "✓ lodash.template already patched with v2"
exit 0
fi
# Remove old patch if exists
if grep -q "LODASH_TEMPLATE_PATCHED" "$LODGASH_TEMPLATE"; then
echo "🔄 Removing old patch..."
# Use sed to remove old patch (multi-line)
sed -i '/\/\* LODASH_TEMPLATE_PATCHED \*\//,/^}$/d' "$LODGASH_TEMPLATE"
fi
echo "🔧 Patching lodash.template with v2..."
# Create a temporary file with the patch
PATCH='/* LODASH_TEMPLATE_PATCHED_V2 */
// assignWith polyfill for lodash.template - Fixed version
function assignWith(object, source, customizer) {
if (object == null) return object;
if (typeof customizer !== "function") {
customizer = function(objValue, srcValue) {
return srcValue;
};
}
var props = Object.keys(Object(source));
for (var i = 0; i < props.length; i++) {
var key = props[i];
var value = source[key];
var assignedValue = customizer(object[key], value, key, object, source);
if (assignedValue !== undefined) {
object[key] = assignedValue;
}
}
return object;
}
'
# Insert after the first line (license comment)
{
head -n 1 "$LODGASH_TEMPLATE"
echo "$PATCH"
tail -n +2 "$LODGASH_TEMPLATE"
} > "$LODGASH_TEMPLATE.tmp" && mv "$LODGASH_TEMPLATE.tmp" "$LODGASH_TEMPLATE"
echo "✅ Successfully patched lodash.template with assignWith function (v2)"

View File

@@ -9,13 +9,24 @@
"predev": "node utils/check.js dev && vdoing", "predev": "node utils/check.js dev && vdoing",
"prebuild": "node utils/check.js build && vdoing", "prebuild": "node utils/check.js build && vdoing",
"updateTheme": "yarn remove vuepress-theme-vdoing && rm -rf node_modules && yarn && yarn add vuepress-theme-vdoing -D", "updateTheme": "yarn remove vuepress-theme-vdoing && rm -rf node_modules && yarn && yarn add vuepress-theme-vdoing -D",
"editFm": "node utils/editFrontmatter.js" "editFm": "node utils/editFrontmatter.js",
"fix-lodash": "node fix-lodash-manual.js",
"build:fixed": "npm run fix-lodash && npm run build",
"build:win:fixed": "npm run fix-lodash && npm run build:win"
}, },
"license": "MIT", "license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"overrides": {
"lodash": "4.17.21",
"lodash.template": "4.5.0"
},
"devDependencies": { "devDependencies": {
"dayjs": "^1.9.7", "dayjs": "^1.9.7",
"inquirer": "^7.1.0", "inquirer": "^7.1.0",
"json2yaml": "^1.1.0", "json2yaml": "^1.1.0",
"lodash": "4.17.21",
"vuepress": "1.9.9", "vuepress": "1.9.9",
"vuepress-plugin-baidu-tongji": "^1.0.1", "vuepress-plugin-baidu-tongji": "^1.0.1",
"vuepress-plugin-demo-block": "^0.7.2", "vuepress-plugin-demo-block": "^0.7.2",

View File

@@ -0,0 +1,60 @@
import request from '@/utils/request';
/**
* 检验项目维护专属 API操作 lab_activity_definition 表)
* 后端路径前缀:/lab/activity-definition
*/
// 分页查询检验项目列表
export function getLabActivityDefinitionPage(query) {
return request({
url: '/lab/activity-definition/page',
method: 'get',
params: query,
});
}
// 查询检验项目详情
export function getLabActivityDefinitionOne(id) {
return request({
url: '/lab/activity-definition/one',
method: 'get',
params: { id },
});
}
// 新增检验项目
export function addLabActivityDefinition(data) {
return request({
url: '/lab/activity-definition/add',
method: 'post',
data: data,
});
}
// 编辑检验项目
export function editLabActivityDefinition(data) {
return request({
url: '/lab/activity-definition/edit',
method: 'put',
data: data,
});
}
// 停用检验项目
export function stopLabActivityDefinition(ids) {
return request({
url: '/lab/activity-definition/stop',
method: 'put',
data: ids,
});
}
// 启用检验项目
export function startLabActivityDefinition(ids) {
return request({
url: '/lab/activity-definition/start',
method: 'put',
data: ids,
});
}

View File

@@ -29,10 +29,17 @@ export function formatDateStr(cellValue, format = 'YYYY-MM-DD HH:mm:ss') {
const minutes = String(date.getMinutes()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0');
// 支持不带前导零的格式
const monthNoPad = String(date.getMonth() + 1);
const dayNoPad = String(date.getDate());
return format return format
.replace('YYYY', year) .replace('YYYY', year)
.replace('MM', month) .replace('MM', month)
.replace('DD', day) .replace('DD', day)
.replace('M/', monthNoPad + '/') // 支持 M/ 格式
.replace('D ', dayNoPad + ' ') // 支持 D 格式(后跟空格)
.replace('/D', '/' + dayNoPad) // 支持 /D 格式
.replace('HH', hours) .replace('HH', hours)
.replace('mm', minutes) .replace('mm', minutes)
.replace('ss', seconds); .replace('ss', seconds);

View File

@@ -162,3 +162,61 @@ export const STATUS = {
NORMAL: '0', // 正常/启用 NORMAL: '0', // 正常/启用
DISABLE: '1' // 停用 DISABLE: '1' // 停用
}; };
/**
* 号源槽位状态(与后端 CommonConstants.SlotStatus 保持一致)
* adm_schedule_slot.status 字段
*/
export const SlotStatus = {
/** 可用 / 待预约 */
AVAILABLE: 0,
/** 已预约 */
BOOKED: 1,
/** 已取消 / 已停诊 */
CANCELLED: 2,
/** 已锁定 */
LOCKED: 3,
/** 已签到 / 已取号 */
CHECKED_IN: 4,
};
/**
* 号源槽位状态说明信息
*/
export const SlotStatusDescriptions = {
0: '未预约',
1: '已预约',
2: '已停诊',
3: '已锁定',
4: '已取号',
};
/**
* 号源槽位状态对应的CSS类名
*/
export const SlotStatusClassMap = {
'未预约': 'status-unbooked',
'已预约': 'status-booked',
'已取号': 'status-checked',
'已停诊': 'status-cancelled',
'已取消': 'status-cancelled',
'已锁定': 'status-locked',
};
/**
* 获取号源槽位状态的说明
* @param {number} value - 状态值
* @returns {string} - 说明信息
*/
export function getSlotStatusDescription(value) {
return SlotStatusDescriptions[value] || '未知状态';
}
/**
* 获取号源槽位状态对应的CSS类名
* @param {string} status - 状态说明
* @returns {string} - CSS类名
*/
export function getSlotStatusClass(status) {
return SlotStatusClassMap[status] || 'status-unbooked';
}

View File

@@ -37,6 +37,7 @@
<option value="booked">已预约</option> <option value="booked">已预约</option>
<option value="checked">已取号</option> <option value="checked">已取号</option>
<option value="cancelled">已停诊</option> <option value="cancelled">已停诊</option>
<option value="returned">已退号</option>
</select> </select>
</div> </div>
<div id="patientSearch" class="patient-search"> <div id="patientSearch" class="patient-search">
@@ -105,7 +106,7 @@
<div class="ticket-grid" v-if="filteredTickets.length > 0"> <div class="ticket-grid" v-if="filteredTickets.length > 0">
<div v-for="(item, index) in filteredTickets" :key="item.slot_id" class="ticket-card" @dblclick="handleDoubleClick(item)" @contextmenu.prevent="handleRightClick($event, item)"> <div v-for="(item, index) in filteredTickets" :key="item.slot_id" class="ticket-card" @dblclick="handleDoubleClick(item)" @contextmenu.prevent="handleRightClick($event, item)">
<!-- 序号放在最右侧 --> <!-- 序号放在最右侧 -->
<div class="ticket-index">{{ index + 1 }}</div> <div class="ticket-index">{{ item.seqNo != null ? item.seqNo : index + 1 }}</div>
<!-- 1.时间 --> <!-- 1.时间 -->
<div class="ticket-id-time">{{ item.dateTime }}</div> <div class="ticket-id-time">{{ item.dateTime }}</div>
<!-- 2. 状态标签 --> <!-- 2. 状态标签 -->
@@ -125,7 +126,7 @@
<div class="ticket-type">{{ item.ticketType === 'general' ? '普通' : '专家' }}</div> <div class="ticket-type">{{ item.ticketType === 'general' ? '普通' : '专家' }}</div>
<!-- 7. 已预约患者信息 --> <!-- 7. 已预约患者信息 -->
<div v-if="(item.status === '已预约' || item.status === '已取号') && item.patientName" class="ticket-patient"> <div v-if="(item.status === '已预约' || item.status === '已取号') && item.patientName" class="ticket-patient">
{{ item.patientName }}({{ item.patientId }}) {{ item.patientName }}({{ item.patientId }},{{ getGenderText(item.gender || item.patientGender) }})
</div> </div>
<div class="ticket-actions"> <div class="ticket-actions">
<button class="action-button book-button" @click="openPatientSelectModal(item.slot_id)" :disabled="item.status !== '未预约'" :class="{ 'disabled': item.status !== '未预约' }"> <button class="action-button book-button" @click="openPatientSelectModal(item.slot_id)" :disabled="item.status !== '未预约'" :class="{ 'disabled': item.status !== '未预约' }">
@@ -194,7 +195,8 @@
> >
<td>{{ index + 1 }}</td> <td>{{ index + 1 }}</td>
<td>{{ patient.name }}</td> <td>{{ patient.name }}</td>
<td>{{ patient.id || patient.medicalCard }}</td> <td>{{ patient.identifierNo || patient.medicalCard || patient.id }}</td>
<td>{{ patient.identifierNo }}</td>
<td>{{ getGenderText(patient.genderEnum_enumText || patient.genderEnum || patient.gender || patient.sex) }}</td> <td>{{ getGenderText(patient.genderEnum_enumText || patient.genderEnum || patient.gender || patient.sex) }}</td>
<td>{{ patient.idCard }}</td> <td>{{ patient.idCard }}</td>
<td>{{ patient.phone }}</td> <td>{{ patient.phone }}</td>
@@ -254,6 +256,7 @@ const STATUS_CLASS_MAP = {
'未预约': 'status-unbooked', '未预约': 'status-unbooked',
'已预约': 'status-booked', '已预约': 'status-booked',
'已取号': 'status-checked', '已取号': 'status-checked',
'已退号': 'status-returned',
'已停诊': 'status-cancelled', '已停诊': 'status-cancelled',
'已取消': 'status-cancelled' '已取消': 'status-cancelled'
}; };
@@ -364,14 +367,23 @@ export default {
this.searchPatients(); this.searchPatients();
}, },
getPatientUniqueId(patient, index = null) { getPatientUniqueId(patient, index = null) {
return patient.idCard || patient.medicalCard || patient.id || (index !== null ? `temp_${index}` : null); return patient.id || patient.patientId || patient.identifierNo || patient.medicalCard || patient.idCard ||
(index !== null ? `temp_${index}` : null);
}, },
matchPatientKeyword(patient, keyword) { matchPatientKeyword(patient, keyword) {
const normalizedKeyword = String(keyword || '').trim().toLowerCase(); const normalizedKeyword = String(keyword || '').trim().toLowerCase();
if (!normalizedKeyword) { if (!normalizedKeyword) {
return true; return true;
} }
const fields = [patient.name, patient.id, patient.medicalCard, patient.idCard, patient.phone]; const fields = [
patient.name,
patient.id,
patient.patientId,
patient.identifierNo,
patient.medicalCard,
patient.idCard,
patient.phone
];
return fields.some(field => String(field || '').toLowerCase().includes(normalizedKeyword)); return fields.some(field => String(field || '').toLowerCase().includes(normalizedKeyword));
}, },
searchPatients() { searchPatients() {
@@ -394,6 +406,8 @@ export default {
const rowKey = this.getPatientUniqueId(patient, index); const rowKey = this.getPatientUniqueId(patient, index);
return { return {
...patient, ...patient,
// 统一口径:预约场景的就诊卡号使用患者标识表中的 identifierNo
medicalCard: patient.identifierNo || patient.medicalCard || '',
_rowKey: rowKey _rowKey: rowKey
}; };
}); });
@@ -494,6 +508,7 @@ export default {
this.tickets[ticketIndex].patientName = null; this.tickets[ticketIndex].patientName = null;
this.tickets[ticketIndex].patientId = null; this.tickets[ticketIndex].patientId = null;
this.tickets[ticketIndex].patientGender = null; this.tickets[ticketIndex].patientGender = null;
this.tickets[ticketIndex].gender = null;
this.tickets[ticketIndex].medicalCard = null; this.tickets[ticketIndex].medicalCard = null;
this.tickets[ticketIndex].phone = null; this.tickets[ticketIndex].phone = null;
} }
@@ -554,12 +569,22 @@ export default {
try { try {
const userStore = useUserStore(); const userStore = useUserStore();
const patientPrimaryId = this.selectedPatient.id || this.selectedPatient.patientId;
const medicalCard = this.selectedPatient.identifierNo || this.selectedPatient.medicalCard;
if (!patientPrimaryId) {
ElMessage.error('患者ID缺失无法预约请重新选择患者');
return;
}
if (!medicalCard) {
ElMessage.error('就诊卡号缺失,无法预约,请先维护患者就诊卡号');
return;
}
const appointmentData = { const appointmentData = {
ticketId: this.currentTicket.slot_id, ticketId: this.currentTicket.slot_id,
slotId: this.currentTicket.slot_id, slotId: this.currentTicket.slot_id,
patientId: this.selectedPatient.id || this.selectedPatient.idCard || this.selectedPatient.medicalCard, patientId: patientPrimaryId,
patientName: this.selectedPatient.name, patientName: this.selectedPatient.name,
medicalCard: this.selectedPatient.medicalCard || this.selectedPatient.id, medicalCard,
phone: this.selectedPatient.phone, phone: this.selectedPatient.phone,
gender: this.getGenderValueForBackend(this.selectedPatient), gender: this.getGenderValueForBackend(this.selectedPatient),
fee: Number(this.currentTicket.fee) || 0, fee: Number(this.currentTicket.fee) || 0,
@@ -578,8 +603,9 @@ export default {
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
this.tickets[ticketIndex].status = '已预约'; this.tickets[ticketIndex].status = '已预约';
this.tickets[ticketIndex].patientName = this.selectedPatient.name; this.tickets[ticketIndex].patientName = this.selectedPatient.name;
this.tickets[ticketIndex].patientId = this.selectedPatient.medicalCard || this.selectedPatient.id; this.tickets[ticketIndex].patientId = medicalCard;
this.tickets[ticketIndex].patientGender = this.selectedPatient.genderEnum_enumText || this.selectedPatient.genderEnum || this.selectedPatient.gender || this.selectedPatient.sex; this.tickets[ticketIndex].patientGender = this.selectedPatient.genderEnum_enumText || this.selectedPatient.genderEnum || this.selectedPatient.gender || this.selectedPatient.sex;
this.tickets[ticketIndex].gender = this.getGenderText(this.tickets[ticketIndex].patientGender);
} }
this.closePatientSelectModal(); this.closePatientSelectModal();
@@ -601,9 +627,9 @@ export default {
}, },
// 获取性别文本 // 获取性别文本
getGenderText(genderValue) { getGenderText(genderValue) {
// 如果值为null或undefined返回'-' // 如果值为null或undefined返回"未知"
if (genderValue === null || genderValue === undefined) { if (genderValue === null || genderValue === undefined) {
return '-'; return '未知';
} }
// 将值转换为字符串进行比较 // 将值转换为字符串进行比较
@@ -623,8 +649,8 @@ export default {
return '女'; return '女';
} }
// 如果都不是,返回原值或'-' // 如果都不是,返回"未知"
return '-'; return '未知';
}, },
// 获取用于后端的性别值 // 获取用于后端的性别值
getGenderValueForBackend(patient) { getGenderValueForBackend(patient) {
@@ -703,10 +729,36 @@ export default {
return; return;
} }
const records = payload.list || payload.records || []; const records = payload.list || payload.records || [];
const filteredRecords = this.applyStatusFilter(records);
const total = Number(payload.total); const total = Number(payload.total);
this.tickets = [...records]; this.tickets = [...filteredRecords];
this.allTickets = [...records]; this.allTickets = [...filteredRecords];
// 当按状态筛选时,优先使用前端过滤后的数量,避免后端状态未生效导致“显示全部”
if (this.selectedStatus && this.selectedStatus !== 'all') {
this.totalTickets = this.tickets.length;
} else {
this.totalTickets = Number.isFinite(total) ? total : this.tickets.length; this.totalTickets = Number.isFinite(total) ? total : this.tickets.length;
}
},
applyStatusFilter(records = []) {
if (!Array.isArray(records) || records.length === 0) {
return [];
}
if (!this.selectedStatus || this.selectedStatus === 'all') {
return records;
}
const statusMap = {
unbooked: ['未预约'],
booked: ['已预约'],
checked: ['已取号'],
cancelled: ['已停诊', '已取消'],
returned: ['已退号']
};
const matchedStatusList = statusMap[this.selectedStatus] || [];
if (matchedStatusList.length === 0) {
return records;
}
return records.filter(item => matchedStatusList.includes(item?.status));
}, },
updateDoctorsListFromApi(doctorResponse) { updateDoctorsListFromApi(doctorResponse) {
let doctorList = []; let doctorList = [];
@@ -1376,6 +1428,11 @@ export default {
color: #52c41a; color: #52c41a;
} }
.status-returned {
background-color: #fff7e6;
color: #d46b08;
}
.status-cancelled { .status-cancelled {
background-color: #fff1f0; background-color: #fff1f0;
color: #ff4d4f; color: #ff4d4f;

View File

@@ -571,7 +571,7 @@
<script setup> <script setup>
import {getOrgTree, saveOrderGroup} from './api'; import {getOrgTree, saveOrderGroup} from './api';
import adviceBaseList from './adviceBaseList'; import adviceBaseList from './adviceBaseList';
import {getCurrentInstance, nextTick, watch} from 'vue'; import {computed, getCurrentInstance, nextTick, ref, watch} from 'vue';
import {calculateQuantityByDays, formatNumber} from '@/utils/his'; import {calculateQuantityByDays, formatNumber} from '@/utils/his';
const emit = defineEmits(['selectDiagnosis']); const emit = defineEmits(['selectDiagnosis']);
@@ -620,31 +620,31 @@ const stockList = ref([]);
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
const inputRefs = ref({}); // 存储输入框实例 const inputRefs = ref({}); // 存储输入框实例
const requiredProps = ref([]); // 存储必填项 prop 顺序 const requiredProps = ref([]); // 存储必填项 prop 顺序
const { method_code, unit_code, rate_code, distribution_category_code } = proxy.useDict( const { method_code, unit_code, rate_code, distribution_category_code, drord_doctor_type } = proxy.useDict(
'method_code', 'method_code',
'unit_code', 'unit_code',
'rate_code', 'rate_code',
'distribution_category_code' 'distribution_category_code',
'drord_doctor_type'
); );
const adviceTypeList = ref([ // 使用 drord_doctor_type 字典
{ const adviceTypeList = computed(() => {
label: '西药中成药', if (drord_doctor_type.value && drord_doctor_type.value.length > 0) {
value: 1, const list = drord_doctor_type.value.map(item => ({
}, label: item.label,
{ value: parseInt(item.value) || item.value
label: '耗材', }));
value: 2, return [...list, { label: '全部', value: undefined }];
}, }
{ // 默认值
label: '诊疗', return [
value: 3, { label: '西药中成药', value: 1 },
}, { label: '耗材', value: 4 },
{ { label: '诊疗', value: 3 },
label: '全部', { label: '全部', value: undefined },
value: undefined, ];
}, });
]);
onMounted(() => { onMounted(() => {
document.addEventListener('keydown', escKeyListener); document.addEventListener('keydown', escKeyListener);
}); });

View File

@@ -7,6 +7,7 @@
<div style="display: flex; align-items: center; width: 100%"> <div style="display: flex; align-items: center; width: 100%">
<span style="font-size: 16px; font-weight: bold; margin-right: 20px;">门诊挂号</span> <span style="font-size: 16px; font-weight: bold; margin-right: 20px;">门诊挂号</span>
<div style="flex: 1; display: flex; justify-content: center; align-items: center;"> <div style="flex: 1; display: flex; justify-content: center; align-items: center;">
<el-button type="success" icon="Check" @click="handleCheckIn" size="small">预约签到</el-button>
<el-button type="primary" icon="Document" @click="goToPatientRecord" size="small">档案</el-button> <el-button type="primary" icon="Document" @click="goToPatientRecord" size="small">档案</el-button>
<el-button type="primary" icon="Plus" @click="handleAddPatient" size="small">新建</el-button> <el-button type="primary" icon="Plus" @click="handleAddPatient" size="small">新建</el-button>
<el-button type="primary" plain icon="Search" @click="handleSearch" size="small">查询</el-button> <el-button type="primary" plain icon="Search" @click="handleSearch" size="small">查询</el-button>
@@ -15,7 +16,7 @@
<el-button type="primary" plain @click="handleReadCard('03')" size="small">医保卡</el-button> <el-button type="primary" plain @click="handleReadCard('03')" size="small">医保卡</el-button>
<el-button type="warning" plain icon="CircleClose" @click="handleClear" size="small">清空</el-button> <el-button type="warning" plain icon="CircleClose" @click="handleClear" size="small">清空</el-button>
<el-button type="primary" icon="Plus" @click="handleAdd" size="small">保存挂号</el-button> <el-button type="primary" icon="Plus" @click="handleAdd" size="small">保存挂号</el-button>
<el-button type="success" icon="Printer" @click="handleReprint" size="small">补打挂号</el-button> <el-button type="info" icon="Printer" @click="handleReprint" size="small">补打挂号</el-button>
</div> </div>
</div> </div>
</template> </template>
@@ -615,6 +616,74 @@
} }
" "
/> />
<!-- 预约签到患者选择弹窗 -->
<el-dialog
v-model="showCheckInPatientModal"
title="请选择预约的患者"
width="1200px"
:close-on-click-modal="false"
>
<div style="margin-bottom: 20px; display: flex; gap: 10px;">
<el-input
v-model="checkInSearchKey"
placeholder="输入患者姓名回车查询"
style="width: 400px"
@keyup.enter="loadCheckInPatientList"
/>
<el-button type="primary" @click="loadCheckInPatientList">查询</el-button>
</div>
<el-table
v-loading="checkInLoading"
:data="checkInPatientList"
border
style="width: 100%"
@row-click="selectRow"
highlight-current-row
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="patientId" label="就诊卡号" width="120" align="center" />
<el-table-column prop="patientName" label="姓名" width="120" align="center">
<template #default="scope">
<span style="color: #ff4d4f">{{ scope.row.patientName }}</span>
</template>
</el-table-column>
<el-table-column prop="gender" label="性别" width="80" align="center" />
<el-table-column label="证件类型" width="150" align="center">
<template #default>居民身份证</template>
</el-table-column>
<el-table-column prop="idCard" label="证件号码" width="200" align="center" />
<el-table-column prop="phone" label="手机号码" width="150" align="center" />
<el-table-column label="号源类型" width="100" align="center">
<template #default="scope">
<el-tag :type="scope.row.ticketType === 'expert' ? 'danger' : 'success'">
{{ scope.row.ticketType === 'expert' ? '专家号' : '普通号' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="fee" label="预约金额" width="100" align="center">
<template #default="scope">
<span style="font-weight: bold; color: #f5222d">{{ scope.row.fee }}</span>
</template>
</el-table-column>
<el-table-column prop="dateTime" label="就诊时间" width="180" align="center" />
</el-table>
<div style="margin-top: 20px; display: flex; justify-content: space-between; align-items: center;">
<el-pagination
v-model:current-page="checkInPage"
v-model:page-size="checkInLimit"
:total="checkInTotal"
layout="prev, pager, next"
@current-change="loadCheckInPatientList"
/>
<div class="dialog-footer">
<el-button @click="showCheckInPatientModal = false">取消</el-button>
<el-button type="primary" @click="confirmCheckIn" :disabled="!selectedCheckInPatient">确定</el-button>
</div>
</div>
</el-dialog>
</div> </div>
</template> </template>
@@ -632,6 +701,7 @@ import {
returnRegister, returnRegister,
updatePatientPhone, updatePatientPhone,
} from './components/outpatientregistration'; } from './components/outpatientregistration';
import { listTicket, checkInTicket } from '@/api/appoinmentmanage/ticket';
import { invokeYbPlugin5000, invokeYbPlugin5001 } from '@/api/public'; import { invokeYbPlugin5000, invokeYbPlugin5001 } from '@/api/public';
import patientInfoDialog from './components/patientInfoDialog'; import patientInfoDialog from './components/patientInfoDialog';
import PatientAddDialog from './components/patientAddDialog'; import PatientAddDialog from './components/patientAddDialog';
@@ -644,7 +714,7 @@ import {handleColor} from '@/utils/his';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import {formatDateStr} from '@/utils/index'; import {formatDateStr} from '@/utils/index';
import {isValidCNPhoneNumber} from '../../../utils/validate'; import {isValidCNPhoneNumber} from '../../../utils/validate';
import {ElMessage} from 'element-plus'; import {ElMessage, ElMessageBox} from 'element-plus';
import {hiprint} from 'vue-plugin-hiprint'; import {hiprint} from 'vue-plugin-hiprint';
import outpatientRegistrationTemplate from '@/components/Print/OutpatientRegistration.json'; import outpatientRegistrationTemplate from '@/components/Print/OutpatientRegistration.json';
@@ -687,14 +757,25 @@ const ybTypeRef = ref(null);
const openDialog = ref(false); const openDialog = ref(false);
const openRefundDialog = ref(false); const openRefundDialog = ref(false);
const openReprintDialog = ref(false); const openReprintDialog = ref(false);
// 预约签到相关变量
const showCheckInPatientModal = ref(false);
const checkInPatientList = ref([]);
const selectedCheckInPatient = ref(null);
const totalAmount = ref(0); const totalAmount = ref(0);
const chargeItemIdList = ref([]); const chargeItemIdList = ref([]);
const chrgBchnoList = ref([]); const chrgBchnoList = ref([]);
const paymentId = ref(''); const paymentId = ref('');
const loadingText = ref(''); const loadingText = ref('');
const checkInSearchKey = ref('');
const checkInPage = ref(1);
const checkInLimit = ref(10);
const checkInTotal = ref(0);
const checkInLoading = ref(false);
const registerInfo = ref({}); // 原挂号记录信息 const registerInfo = ref({}); // 原挂号记录信息
const queryType = ref('all'); // 查询类型all-全部, normal-正常挂号, returned-退号记录 const queryType = ref('all'); // 查询类型all-全部, normal-正常挂号, returned-退号记录
const guardianAgeConfig = ref(''); // 监护人规定年龄配置 const guardianAgeConfig = ref(''); // 监护人规定年龄配置
const currentSlotId = ref(null); // 当前预约签到的号源ID
// 使用 ref 定义查询所得用户信息数据 // 使用 ref 定义查询所得用户信息数据
const patientInfoList = ref(undefined); const patientInfoList = ref(undefined);
@@ -1584,6 +1665,189 @@ function handleReprint() {
openReprintDialog.value = true; openReprintDialog.value = true;
} }
/** 预约签到 - 打开患者选择弹窗 */
function handleCheckIn() {
// 打开患者选择弹窗,显示已预约但未签到的患者列表
showCheckInPatientModal.value = true;
// 加载已预约未签到的患者列表
loadCheckInPatientList();
}
/** 加载预约签到患者列表 */
function loadCheckInPatientList() {
checkInLoading.value = true;
const today = formatDateStr(new Date(), 'YYYY-MM-DD');
listTicket({
date: today,
status: 'booked',
name: checkInSearchKey.value, // 支持姓名等模糊查询,后端需适配
page: checkInPage.value,
limit: checkInLimit.value
}).then(res => {
const data = res.data?.list || res.list || res.data || [];
const total = res.data?.total || res.total || data.length;
checkInPatientList.value = data.map(item => ({
...item,
appointmentDate: item.scheduleDate + ' ' + (item.expectTime || '')
}));
checkInTotal.value = total;
}).catch(err => {
console.error('加载预约导出失败:', err);
ElMessage.error('获取预约列表失败');
}).finally(() => {
checkInLoading.value = false;
});
}
/** 弹窗行点击处理 */
function selectRow(row) {
selectedCheckInPatient.value = row;
}
/** 确认签到(一键签到:直接构建挂号参数 → 预结算 → 弹收费窗口) */
async function confirmCheckIn() {
if (!selectedCheckInPatient.value) {
ElMessage.warning('请先选择患者');
return;
}
const patient = selectedCheckInPatient.value;
// 每次开始新的签到流程先清理残留 slotId避免历史脏值串单
currentSlotId.value = null;
// 弹出确认提示
try {
await ElMessageBox.confirm(
`确认为患者【${patient.patientName}】办理签到挂号?\n` +
`科室:${patient.department || '-'}\n` +
`医生:${patient.doctor || '-'}\n` +
`费用:¥${patient.fee || '0.00'}`,
'签到确认',
{
confirmButtonText: '确认签到',
cancelButtonText: '取消',
type: 'info',
}
);
} catch {
// 用户点了取消
return;
}
showCheckInPatientModal.value = false;
readCardLoading.value = true;
loadingText.value = '正在处理签到挂号...';
try {
// 1. 用科室ID加载该科室的挂号类型列表获取 serviceTypeId 和 definitionId
const healthcareRes = await getHealthcareMetadata({ organizationId: patient.departmentId });
const healthcareRecords = healthcareRes.data?.records || [];
if (healthcareRecords.length === 0) {
ElMessage.error('该科室未配置挂号类型,无法自动签到');
readCardLoading.value = false;
return;
}
// 2. 按号源类型(专家/普通)模糊匹配挂号类型
const matchTypeName = (patient.ticketType === 'expert') ? '专家' : '普通';
const matchedService = healthcareRecords.find(h => h.name && h.name.includes(matchTypeName));
if (!matchedService) {
// 匹配不到就取第一个作为兜底
ElMessage.warning('未精确匹配到挂号类型,已使用默认类型');
}
const service = matchedService || healthcareRecords[0];
const realPatientId = patient.realPatientId; // 后端新增的真实患者数据库ID
if (!realPatientId) {
ElMessage.error('患者ID缺失请联系管理员检查预约数据');
readCardLoading.value = false;
return;
}
// 3. 构建挂号参数(与 transformFormData 结构一致)
const registrationParam = {
encounterFormData: {
patientId: realPatientId,
priorityEnum: 3, // 默认优先级
serviceTypeId: service.id,
organizationId: patient.departmentId,
},
encounterLocationFormData: {
locationId: null,
},
encounterParticipantFormData: {
practitionerId: patient.doctorId,
},
accountFormData: {
patientId: realPatientId,
typeCode: 1, // 个人现金账户
contractNo: '0000', // 默认自费
},
chargeItemFormData: {
patientId: realPatientId,
definitionId: service.definitionId,
serviceId: service.id,
totalPrice: parseFloat(patient.fee) || ((service.price || 0) + (service.activityPrice || 0)),
},
};
// 4. 设置 patientInfoChargeDialog 需要展示)
patientInfo.value = {
patientId: realPatientId,
patientName: patient.patientName,
genderEnum_enumText: patient.gender || '-',
age: '',
contractName: '自费',
idCard: patient.idCard,
phone: patient.phone,
categoryEnum: '门诊',
organizationName: patient.department || '',
practitionerName: patient.doctor || '',
healthcareName: service.name || '',
};
// 同步设置 form 的 contractNoChargeDialog 的 feeType 会读取它
form.value.contractNo = '0000';
// 5. 调用预结算接口reg-pre-pay
const res = await addOutpatientRegistration(registrationParam);
if (res.code == 200) {
// 仅在预结算成功后记录待签到的号源,避免失败路径残留脏数据
currentSlotId.value = patient.slot_id;
// 6. 设置收费弹窗所需的数据
chrgBchno.value = res.data.chrgBchno;
registerBusNo.value = res.data.busNo;
totalAmount.value = res.data.psnCashPay;
patientInfo.value.encounterId = res.data.encounterId || '';
patientInfo.value.busNo = res.data.busNo || '';
transformedData.value = registrationParam;
chargeItemIdList.value = [];
// 7. 打开收费弹窗
openDialog.value = true;
// 打印挂号单
printRegistrationByHiprint(res.data);
} else {
currentSlotId.value = null;
ElMessage.error(res.msg || '预结算失败');
}
} catch (err) {
currentSlotId.value = null;
console.error('预约签到失败:', err);
ElMessage.error('签到处理失败: ' + (err.message || '未知错误'));
} finally {
readCardLoading.value = false;
}
}
/** /**
* 点击患者列表给表单赋值 * 点击患者列表给表单赋值
*/ */
@@ -1656,20 +1920,29 @@ function handleClose(value) {
proxy.$modal.msgSuccess('操作成功'); proxy.$modal.msgSuccess('操作成功');
// 更新患者手机号 // 更新患者手机号
updatePhone(); updatePhone();
// getList();
// reset(); // 先取出并清空,避免接口失败/取消等路径导致 slotId 残留污染下一单
// addOutpatientRegistration(transformedData.value).then((response) => { const pendingSlotId = currentSlotId.value;
// reset(); currentSlotId.value = null;
// proxy.$modal.msgSuccess('新增成功');
// getList(); // 如果是预约签到的挂号,执行签到状态更新
// }); if (pendingSlotId) {
checkInTicket(pendingSlotId).then(() => {
console.log('预约状态已更新为已取号');
}).catch(err => {
console.error('更新预约状态失败:', err);
ElMessage.error('预约状态更新失败,请手动签到');
});
}
} else if (value == 'cancel') { } else if (value == 'cancel') {
currentSlotId.value = null;
// cancelRegister(patientInfo.value.encounterId).then((res) => { // cancelRegister(patientInfo.value.encounterId).then((res) => {
// if (res.code == 200) { // if (res.code == 200) {
// getList(); // getList();
// } // }
// }); // });
} else { } else {
currentSlotId.value = null;
openRefundDialog.value = false; openRefundDialog.value = false;
} }
} }

View File

@@ -53,11 +53,14 @@ const currentSelectRow = ref({});
const queryParams = ref({ const queryParams = ref({
pageSize: 100, pageSize: 100,
pageNum: 1, pageNum: 1,
adviceType: undefined,
categoryCode: '',
}); });
const adviceBaseList = ref([]); const adviceBaseList = ref([]);
// 节流函数 // 节流函数
const throttledGetList = throttle( const throttledGetList = throttle(
() => { () => {
// 触发数据加载
getList(); getList();
}, },
300, 300,
@@ -72,6 +75,7 @@ watch(
} }
queryParams.value.searchKey = newValue?.searchKey; queryParams.value.searchKey = newValue?.searchKey;
queryParams.value.adviceType = newValue?.adviceType; queryParams.value.adviceType = newValue?.adviceType;
queryParams.value.categoryCode = newValue?.categoryCode;
throttledGetList(); throttledGetList();
}, },
{ deep: true } { deep: true }
@@ -86,6 +90,11 @@ watch(
if (props.adviceQueryParams) { if (props.adviceQueryParams) {
queryParams.value.searchKey = props.adviceQueryParams.searchKey; queryParams.value.searchKey = props.adviceQueryParams.searchKey;
queryParams.value.adviceType = props.adviceQueryParams.adviceType; queryParams.value.adviceType = props.adviceQueryParams.adviceType;
queryParams.value.categoryCode = props.adviceQueryParams.categoryCode;
console.log('[adviceBaseList] 弹窗打开,参数:', JSON.stringify({
adviceType: queryParams.value.adviceType,
categoryCode: queryParams.value.categoryCode
}));
} }
// 主动触发数据加载 // 主动触发数据加载
getList(); getList();

View File

@@ -18,28 +18,29 @@
@row-dblclick="clickRowDb" @row-dblclick="clickRowDb"
:expand-row-keys="expandOrder" :expand-row-keys="expandOrder"
> >
<el-table-column type="expand" width="1" style="width: 0"> <el-table-column type="expand" width="40">
<template #default="scope"> <template #default="scope">
<el-form :model="scope.row" :rules="rowRules" :ref="'formRef' + scope.$index"> <el-form :model="scope.row" :rules="rowRules" :ref="'formRef' + scope.$index">
<div style="padding: 16px; background: #f8f9fa; border-radius: 8px"> <div style="padding: 16px; background: #f8f9fa; border-radius: 8px">
<template v-if="scope.row.adviceType == 2"> <!-- 药品类型adviceType == 1和耗材类型adviceType == 2使用相同的界面 -->
<template v-if="scope.row.adviceType == 1 || scope.row.adviceType == 2">
<div style="display: flex; align-items: center; margin-bottom: 16px; gap: 16px"> <div style="display: flex; align-items: center; margin-bottom: 16px; gap: 16px">
<span style="font-size: 16px; font-weight: 600"> <span style="font-size: 16px; font-weight: 600">
{{ {{
scope.row.adviceName + scope.row.adviceName +
' ' + ' ' +
scope.row.volume + (scope.row.volume ? scope.row.volume + ' ' : '') +
' ' + (scope.row.unitPrice ? scope.row.unitPrice + ' 元/' : '') +
scope.row.unitPrice + (scope.row.unitCode_dictText || '')
' 元/' +
scope.row.unitCode_dictText
}} }}
</span> </span>
<div class="form-group"> <div class="form-group">
<!-- 库存不为空时显示批号选择 -->
<el-select <el-select
v-if="scope.row.stockList && scope.row.stockList.length > 0"
v-model="scope.row.lotNumber" v-model="scope.row.lotNumber"
style="width: 180px; margin-right: 20px" style="width: 180px; margin-right: 20px"
placeholder="药房" placeholder="选择批号"
> >
<el-option <el-option
v-for="item in scope.row.stockList" v-for="item in scope.row.stockList"
@@ -52,7 +53,7 @@
item.lotNumber + item.lotNumber +
' ' + ' ' +
' 库存:' + ' 库存:' +
item.quantity / scope.row.partPercent + (item.quantity / scope.row.partPercent).toFixed(2) +
item.unitCode_dictText + item.unitCode_dictText +
' 单价:' + ' 单价:' +
item.price.toFixed(2) + item.price.toFixed(2) +
@@ -62,6 +63,10 @@
@click="handleNumberClick(item, scope.$index)" @click="handleNumberClick(item, scope.$index)"
/> />
</el-select> </el-select>
<!-- 库存为空时显示提示 -->
<span v-else style="color: #f56c6c; margin-right: 20px; font-size: 14px;">
无可用库存
</span>
<el-form-item <el-form-item
label="数量:" label="数量:"
prop="quantity" prop="quantity"
@@ -79,9 +84,10 @@
/> />
</el-form-item> </el-form-item>
<el-select <el-select
v-if="scope.row.unitCodeList && scope.row.unitCodeList.length > 0"
v-model="scope.row.unitCode" v-model="scope.row.unitCode"
style="width: 70px; margin-right: 20px" style="width: 70px; margin-right: 20px"
placeholder=" " placeholder="单位"
@change="calculateTotalAmount(scope.row, scope.$index)" @change="calculateTotalAmount(scope.row, scope.$index)"
> >
<template v-for="item in scope.row.unitCodeList" :key="item.value"> <template v-for="item in scope.row.unitCodeList" :key="item.value">
@@ -157,7 +163,7 @@
<el-table-column label="" align="center" prop="groupId" width="60"> <el-table-column label="" align="center" prop="groupId" width="60">
<template #default="scope"> <template #default="scope">
<el-checkbox <el-checkbox
:disabled = "scope.row.bizRequestFlag==0" :disabled = "scope.row.chargeStatus == 5"
v-model="scope.row.check" v-model="scope.row.check"
placeholder="" placeholder=""
@click.stop="" @click.stop=""
@@ -177,13 +183,60 @@
<template v-if="getRowDisabled(scope.row)"> <template v-if="getRowDisabled(scope.row)">
<el-select <el-select
style="width: 35%; margin-right: 20px" style="width: 35%; margin-right: 20px"
v-model="scope.row.adviceType" v-model="scope.row.adviceTypeValue"
:ref="'adviceTypeRef' + scope.$index" :ref="'adviceTypeRef' + scope.$index"
placeholder="选择类型"
@change=" @change="
(value) => { (value) => {
console.log('[类型选择] value:', value);
expandOrder = []; expandOrder = [];
prescriptionList[scope.$index].adviceName = undefined; prescriptionList[scope.$index].adviceName = undefined;
adviceQueryParams.adviceType = value;
// 根据 value 值直接判断
let adviceType, categoryCode, label;
switch (value) {
case '1':
adviceType = 1;
categoryCode = '2';
label = '西药';
break;
case '2':
adviceType = 1;
categoryCode = '1';
label = '中成药';
break;
case '3':
adviceType = 2;
categoryCode = '';
label = '耗材';
break;
case '4':
adviceType = 3;
categoryCode = '';
label = '诊疗';
break;
default:
adviceType = undefined;
categoryCode = '';
label = '';
}
prescriptionList[scope.$index].adviceType = adviceType;
prescriptionList[scope.$index].adviceType_dictText = label;
prescriptionList[scope.$index].categoryCode = categoryCode;
adviceQueryParams.adviceType = adviceType;
adviceQueryParams.categoryCode = categoryCode;
console.log('[类型选择] 设置后:', { adviceType, categoryCode });
}
"
@clear="
() => {
prescriptionList[scope.$index].adviceName = undefined;
prescriptionList[scope.$index].adviceType = undefined;
prescriptionList[scope.$index].adviceType_dictText = '';
prescriptionList[scope.$index].categoryCode = '';
adviceQueryParams.adviceType = undefined;
adviceQueryParams.categoryCode = '';
} }
" "
> >
@@ -192,12 +245,6 @@
:key="item.value" :key="item.value"
:label="item.label" :label="item.label"
:value="item.value" :value="item.value"
@click="
() => {
prescriptionList[scope.$index].adviceType = item.value;
prescriptionList[scope.$index].adviceType_dictText = item.label;
}
"
/> />
</el-select> </el-select>
<el-popover <el-popover
@@ -243,7 +290,8 @@
</el-table-column> </el-table-column>
<el-table-column label="状态" align="center" prop="" width="90"> <el-table-column label="状态" align="center" prop="" width="90">
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.statusEnum == 2" type="success">签发</el-tag> <el-tag v-if="scope.row.chargeStatus == 5" type="success">收费</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 2" type="success">已签发</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 1" type="">待签发</el-tag> <el-tag v-else-if="scope.row.statusEnum == 1" type="">待签发</el-tag>
</template> </template>
</el-table-column> </el-table-column>
@@ -343,17 +391,35 @@ const { method_code, unit_code, rate_code, distribution_category_code } = proxy.
const handleSaveDisabled = ref(false) //签发状态 const handleSaveDisabled = ref(false) //签发状态
const handleSingOutDisabled = ref(false) //签退状态 const handleSingOutDisabled = ref(false) //签退状态
const adviceTypeList = ref([ const adviceTypeList = ref([
{
label: '西药',
value: '1', // 用字符串
adviceType: 1,
categoryCode: '2',
},
{
label: '中成药',
value: '2', // 用字符串
adviceType: 1,
categoryCode: '1',
},
{ {
label: '耗材', label: '耗材',
value: 2, value: '3', // 用字符串
adviceType: 2,
categoryCode: '',
}, },
{ {
label: '诊疗', label: '诊疗',
value: 3, value: '4', // 用字符串
adviceType: 3,
categoryCode: '',
}, },
{ {
label: '全部', label: '全部',
value: undefined, value: '',
adviceType: undefined,
categoryCode: '',
}, },
]); ]);
watch( watch(
@@ -379,9 +445,6 @@ watch(
if(newValue&&newValue.length>0){ if(newValue&&newValue.length>0){
let saveList = prescriptionList.value.filter((item) => { let saveList = prescriptionList.value.filter((item) => {
return item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag) return item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
})
prescriptionList.value.map(k=>{
k.check = false
}) })
console.log(saveList,"prescriptionList.value") console.log(saveList,"prescriptionList.value")
if (saveList.length == 0) { if (saveList.length == 0) {
@@ -391,13 +454,30 @@ watch(
} }
} }
}, },
{ immediate: true } { immediate: true, deep: false }
); );
function getListInfo(addNewRow) { function getListInfo(addNewRow) {
isAdding.value = false; isAdding.value = false;
getPrescriptionList(props.patientInfo.encounterId).then((res) => { getPrescriptionList(props.patientInfo.encounterId).then((res) => {
prescriptionList.value = res.data; // 为每行数据添加 adviceTypeValue 字段,用于类型下拉框显示
prescriptionList.value = (res.data || []).map(item => {
// 根据 adviceType 和 categoryCode 找到对应的 adviceTypeValue
let adviceTypeValue = '';
if (item.adviceType === 1) {
// 药品类型,需要根据 categoryCode 判断是西药还是中成药
if (item.categoryCode === '1') {
adviceTypeValue = '2'; // 中成药
} else {
adviceTypeValue = '1'; // 西药
}
} else if (item.adviceType === 2) {
adviceTypeValue = '3'; // 耗材
} else if (item.adviceType === 3) {
adviceTypeValue = '4'; // 诊疗
}
return { ...item, adviceTypeValue };
});
if (props.activeTab == 'prescription' && addNewRow) { if (props.activeTab == 'prescription' && addNewRow) {
handleAddPrescription(); handleAddPrescription();
} }
@@ -428,6 +508,10 @@ function handleAddPrescription() {
check: false, check: false,
isEdit: true, isEdit: true,
statusEnum: 1, statusEnum: 1,
adviceTypeValue: '',
adviceType: undefined,
adviceType_dictText: '',
categoryCode: '',
}); });
nextTick(() => { nextTick(() => {
proxy.$refs['adviceRef0'].focus(); proxy.$refs['adviceRef0'].focus();
@@ -457,10 +541,9 @@ function handleFocus(row, index) {
prescriptionList.value.forEach((r, i) => { prescriptionList.value.forEach((r, i) => {
if (i !== index) r.showPopover = false; if (i !== index) r.showPopover = false;
}); });
// 如果当前行已选择adviceType同步到adviceQueryParams // 同步当前行的参数到 adviceQueryParams
if (row.adviceType !== undefined) {
adviceQueryParams.value.adviceType = row.adviceType; adviceQueryParams.value.adviceType = row.adviceType;
} adviceQueryParams.value.categoryCode = row.categoryCode || '';
row.showPopover = true; row.showPopover = true;
} }
@@ -557,32 +640,67 @@ async function selectAdviceBase(key, row) {
// 库存列表 + 价格列表拼成批次号的下拉框(非诊疗) // 库存列表 + 价格列表拼成批次号的下拉框(非诊疗)
if (row.adviceType != 3) { if (row.adviceType != 3) {
if (row.inventoryList && row.inventoryList.length == 0) { let hasInventory = false;
expandOrder.value = []; let inventoryWarning = '';
proxy.$modal.msgWarning('该项目无库存');
return; // 检查库存情况
} if (row.inventoryList && row.inventoryList.length > 0) {
stockList.value = row.inventoryList.map((item, index) => { stockList.value = row.inventoryList.map((item, index) => {
return { ...item, ...row.priceList[index] }; return { ...item, ...row.priceList[index] };
}); });
prescriptionList.value[rowIndex.value].stockList = stockList.value; prescriptionList.value[rowIndex.value].stockList = stockList.value;
// 获取默认批次号的库存,如果没有让医生重新选
// 检查是否有可用的库存(数量 > 0
const availableStock = stockList.value.filter(item => item.quantity > 0);
if (availableStock.length > 0) {
hasInventory = true;
} else {
inventoryWarning = '该项目所有批次库存不足,请选择其他库房或补充库存';
}
// 获取默认批次号的库存
let stock = stockList.value.filter((item) => { let stock = stockList.value.filter((item) => {
return item.lotNumber == row.defaultLotNumber; return item.lotNumber == row.defaultLotNumber;
})[0]; })[0];
if (stock != {} && stock != undefined) { if (stock != {} && stock != undefined) {
if (stock.quantity <= 0) { if (stock.quantity > 0) {
proxy.$modal.msgWarning('该项目库存不足,请选择其它库房');
// return;
}
prescriptionList.value[rowIndex.value].lotNumber = stock.lotNumber; prescriptionList.value[rowIndex.value].lotNumber = stock.lotNumber;
prescriptionList.value[rowIndex.value].inventoryId = stock.inventoryId; prescriptionList.value[rowIndex.value].inventoryId = stock.inventoryId;
prescriptionList.value[rowIndex.value].locationId = stock.locationId; prescriptionList.value[rowIndex.value].locationId = stock.locationId;
prescriptionList.value[rowIndex.value].unitPrice = stock.price; prescriptionList.value[rowIndex.value].unitPrice = stock.price;
prescriptionList.value[rowIndex.value].positionName = stock.locationName; prescriptionList.value[rowIndex.value].positionName = stock.locationName;
// 设置默认数量为1并计算总金额 } else {
// 默认批次库存不足,选择第一个可用批次
const firstAvailable = availableStock[0];
if (firstAvailable) {
prescriptionList.value[rowIndex.value].lotNumber = firstAvailable.lotNumber;
prescriptionList.value[rowIndex.value].inventoryId = firstAvailable.inventoryId;
prescriptionList.value[rowIndex.value].locationId = firstAvailable.locationId;
prescriptionList.value[rowIndex.value].unitPrice = firstAvailable.price;
prescriptionList.value[rowIndex.value].positionName = firstAvailable.locationName;
}
}
}
} else {
inventoryWarning = '该项目无库存记录,请选择其他库房或补充库存';
prescriptionList.value[rowIndex.value].stockList = [];
}
// 统一设置默认值
prescriptionList.value[rowIndex.value].quantity = 1; prescriptionList.value[rowIndex.value].quantity = 1;
if (row.priceList && row.priceList.length > 0) {
if (!prescriptionList.value[rowIndex.value].unitPrice) {
prescriptionList.value[rowIndex.value].unitPrice = row.priceList[0].price;
}
}
calculateTotalPrice(prescriptionList.value[rowIndex.value], rowIndex.value); calculateTotalPrice(prescriptionList.value[rowIndex.value], rowIndex.value);
// 如果有库存警告,统一提示
if (inventoryWarning) {
console.log('[库存警告]', inventoryWarning, '药品:', row.adviceName);
// 不弹出警告框,只在控制台记录,避免频繁打扰用户
// 用户可以在保存时看到真正的库存检查结果
} }
} else { } else {
// 诊疗:设置执行科室和价格 // 诊疗:设置执行科室和价格
@@ -660,41 +778,67 @@ function ensureOrgTreeLoaded() {
} }
function handleDelete() { function handleDelete() {
let deleteList = prescriptionList.value // 🔧 修复:使用 groupIndexList 而不是 check 属性
.filter((item) => { // 因为 watch 监听器会在数据更新时重置 check 为 false
return item.check && item.statusEnum == 1; if (groupIndexList.value.length == 0) {
}) proxy.$modal.msgWarning('请选择要删除的项目');
.map((item) => { return;
}
let deleteList = groupIndexList.value.map((index) => {
const item = prescriptionList.value[index];
// 只删除待签发且未收费的项目
if (item.statusEnum != 1 || item.chargeStatus == 5) {
return null;
}
return { return {
requestId: item.requestId, requestId: item.requestId,
dbOpType: '3', dbOpType: '3',
adviceType: item.adviceType, adviceType: item.adviceType,
}; };
}); }).filter(item => item !== null); // 过滤掉已签发或已收费的项目
if (deleteList.length == 0) { if (deleteList.length == 0) {
proxy.$modal.msgWarning('请选择要删除的项目'); proxy.$modal.msgWarning('只能删除待签发且未收费的项目');
return; return;
} }
if (!deleteList[0].requestId) {
prescriptionList.value.shift(); // 删除逻辑:按索引从大到小排序,避免删除后索引变化
const sortedIndexes = groupIndexList.value.sort((a, b) => b - a);
let hasSavedItem = false;
for (const index of sortedIndexes) {
const item = prescriptionList.value[index];
if (item.statusEnum != 1) {
continue; // 跳过已签发的项目
}
if (!item.requestId) {
// 新增的行(未保存到数据库),直接删除
prescriptionList.value.splice(index, 1);
} else { } else {
hasSavedItem = true;
}
}
if (hasSavedItem) {
// 有已保存的行调用后端API删除
savePrescription({ adviceSaveList: deleteList }).then((res) => { savePrescription({ adviceSaveList: deleteList }).then((res) => {
if (res.code == 200) { if (res.code == 200) {
proxy.$modal.msgSuccess('操作成功'); proxy.$modal.msgSuccess('操作成功');
getListInfo(false); getListInfo(false);
} }
}); });
} else {
// 只有新增行,已经在前端删除完成
proxy.$modal.msgSuccess('操作成功');
} }
// groupIndexList.value
// .sort((a, b) => b - a)
// .forEach((item) => {
// prescriptionList.value.splice(item, 1);
// });
// groupIndexList.value = [];
expandOrder.value = []; expandOrder.value = [];
groupIndexList.value = [];
groupList.value = [];
isAdding.value = false; isAdding.value = false;
adviceQueryParams.value.adviceType = undefined; adviceQueryParams.value.adviceType = undefined;
// prescriptionList.value.splice(index, 1);
} }
function handleNumberClick(item, index) { function handleNumberClick(item, index) {
@@ -706,11 +850,21 @@ function handleNumberClick(item, index) {
} }
function changeCheck(value,index,row){ function changeCheck(value,index,row){
if (value) { if (value) {
if (groupIndexList.value.indexOf(index) === -1) {
groupIndexList.value.push(index) groupIndexList.value.push(index)
}
if (groupList.value.indexOf(row) === -1) {
groupList.value.push(row) groupList.value.push(row)
}
} else { } else {
groupIndexList.value.splice(groupIndexList.value.indexOf(index), 1) const idx1 = groupIndexList.value.indexOf(index)
groupList.value.splice(groupList.value.indexOf(index), 1) if (idx1 !== -1) {
groupIndexList.value.splice(idx1, 1)
}
const idx2 = groupList.value.indexOf(row)
if (idx2 !== -1) {
groupList.value.splice(idx2, 1)
}
} }
groupList.value.map(k=>{ groupList.value.map(k=>{
if(k.check){ if(k.check){
@@ -844,14 +998,14 @@ function handleSingOut() {
return item.check; return item.check;
}) })
.filter((item) => { .filter((item) => {
return item.statusEnum == 2&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag) return item.statusEnum == 2 && item.chargeStatus != 5 && (Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
}) })
.map((item) => { .map((item) => {
return item.requestId; return item.requestId;
}); });
console.log(requestIdList,"签退") console.log(requestIdList,"签退")
if (requestIdList.length == 0) { if (requestIdList.length == 0) {
proxy.$modal.msgWarning('未选择可签退的医嘱'); proxy.$modal.msgWarning('未选择可签退的医嘱(已收费项目不可签退)');
return return
} }
singOut(requestIdList).then((res) => { singOut(requestIdList).then((res) => {

View File

@@ -174,7 +174,7 @@
size="small" size="small"
:icon="Delete" :icon="Delete"
@click="handleDelete(scope.row)" @click="handleDelete(scope.row)"
:disabled="scope.row.consultationStatus >= STATUS.ENDED" :disabled="scope.row.consultationStatus >= STATUS.CONFIRMED"
title="作废" title="作废"
/> />
</template> </template>
@@ -666,8 +666,9 @@ const handleSave = async () => {
} }
const handleDelete = async (row) => { const handleDelete = async (row) => {
if (row.consultationStatus >= STATUS.ENDED) { // 已确认(20)、已签名(30)、已完成(40) 状态禁止作废
ElMessage.warning('已结束的会诊申请不可作废') if (row.consultationStatus >= STATUS.CONFIRMED) {
ElMessage.warning('已确认/已签名状态的会诊申请不可作废')
return return
} }

View File

@@ -143,8 +143,8 @@ watch(
() => props.adviceQueryParams, () => props.adviceQueryParams,
(newValue) => { (newValue) => {
queryParams.value.searchKey = newValue.searchKey; queryParams.value.searchKey = newValue.searchKey;
if (newValue.adviceType) { if (newValue.adviceTypes) {
queryParams.value.adviceTypes = [newValue.adviceType].join(','); queryParams.value.adviceTypes = [newValue.adviceTypes].join(',');
} else { } else {
queryParams.value.adviceTypes = '1,2,3'; queryParams.value.adviceTypes = '1,2,3';
} }
@@ -273,7 +273,10 @@ function fetchFromApi(searchKey) {
minUnitCode_dictText: item.minUnitCode_dictText || item.unitCode_dictText || '', minUnitCode_dictText: item.minUnitCode_dictText || item.unitCode_dictText || '',
volume: item.size || item.totalVolume || '', volume: item.size || item.totalVolume || '',
partPercent: item.partPercent || 1, partPercent: item.partPercent || 1,
inventoryList: [], // 🔧 Bug Fix: 如果后端提供了inventoryList则使用否则为空数组
inventoryList: item.inventoryList || [],
// 🔧 Bug Fix: 构造stockList用于库存显示
stockList: item.inventoryList || [],
adviceDefinitionId: item.id, adviceDefinitionId: item.id,
chargeItemDefinitionId: item.id, chargeItemDefinitionId: item.id,
positionId: item.locationId, positionId: item.locationId,
@@ -354,6 +357,11 @@ function handleQuantity(row) {
const totalQuantity = row.inventoryList.reduce((sum, item) => sum + (item.quantity || 0), 0); const totalQuantity = row.inventoryList.reduce((sum, item) => sum + (item.quantity || 0), 0);
return totalQuantity.toString() + row.minUnitCode_dictText; return totalQuantity.toString() + row.minUnitCode_dictText;
} }
// 🔧 Bug Fix: 耗材类型可能没有inventoryList但可能有stockList
if (row.stockList && row.stockList.length > 0) {
const totalQuantity = row.stockList.reduce((sum, item) => sum + (item.quantity || 0), 0);
return totalQuantity.toString() + row.minUnitCode_dictText;
}
return 0; return 0;
} }

View File

@@ -1003,18 +1003,6 @@ export function deleteInspectionApplication(applyNo) {
}); });
} }
/**
* 生成检验申请单号
* 规则LS + YYYYMMDD + 5位流水号每日从1开始递增
* @returns {Promise} { code: 200, data: { applyNo: string } }
*/
export function generateInspectionApplyNo() {
return request({
url: '/doctor-station/inspection/generate-apply-no',
method: 'get',
});
}
/** /**
* 分页获取检验类型列表(分类) * 分页获取检验类型列表(分类)
* @param {Object} queryParams - 查询参数 * @param {Object} queryParams - 查询参数

View File

@@ -280,6 +280,7 @@ import {
saveDiagnosis, saveDiagnosis,
} from '../api'; } from '../api';
import { DIAG_TYPE } from '@/utils/medicalConstants'; import { DIAG_TYPE } from '@/utils/medicalConstants';
import { formatDateStr } from '@/utils';
import diagnosisdialog from '../diagnosis/diagnosisdialog.vue'; import diagnosisdialog from '../diagnosis/diagnosisdialog.vue';
import AddDiagnosisDialog from './addDiagnosisDialog.vue'; import AddDiagnosisDialog from './addDiagnosisDialog.vue';
import diagnosislist from '../diagnosis/diagnosislist.vue'; import diagnosislist from '../diagnosis/diagnosislist.vue';
@@ -628,11 +629,11 @@ async function handleSaveDiagnosis() {
// 开始加载状态,防止重复提交 // 开始加载状态,防止重复提交
saveLoading.value = true; saveLoading.value = true;
// 保存前按排序号排序,并转换日期格式为ISO字符串 // 保存前按排序号排序,并转换日期格式为后端期望的格式 yyyy/M/d HH:mm:ss
const diagnosisChildList = form.value.diagnosisList.map(item => ({ const diagnosisChildList = form.value.diagnosisList.map(item => ({
...item, ...item,
onsetDate: item.onsetDate ? new Date(item.onsetDate).toISOString() : null, onsetDate: item.onsetDate ? formatDateStr(item.onsetDate, 'YYYY/M/D HH:mm:ss') : null,
diagnosisTime: item.diagnosisTime ? new Date(item.diagnosisTime).toISOString() : null diagnosisTime: item.diagnosisTime ? formatDateStr(item.diagnosisTime, 'YYYY/M/D HH:mm:ss') : null
})); }));
// 调用保存诊断接口 // 调用保存诊断接口
@@ -657,7 +658,9 @@ async function handleSaveDiagnosis() {
} }
} catch (error) { } catch (error) {
console.error('保存诊断失败:', error); console.error('保存诊断失败:', error);
proxy.$modal.msgError('保存诊断失败,请稍后重试'); // 显示后端返回的具体错误信息
const errorMsg = error?.response?.data?.msg || error?.message || '保存诊断失败,请稍后重试';
proxy.$modal.msgError(errorMsg);
} finally { } finally {
// 结束加载状态 // 结束加载状态
saveLoading.value = false; saveLoading.value = false;

View File

@@ -164,7 +164,14 @@
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="检查方法"> <el-form-item label="检查方法">
<el-input v-model="form.inspectionMethod" readonly /> <el-select v-model="form.inspectionMethod" placeholder="请选择" clearable filterable style="width: 100%;">
<el-option
v-for="method in availableMethods"
:key="method.id"
:label="method.name"
:value="method.name"
/>
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@@ -308,6 +315,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import { Printer, Delete } from '@element-plus/icons-vue'; import { Printer, Delete } from '@element-plus/icons-vue';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import request from '@/utils/request'; import request from '@/utils/request';
import { listCheckMethod } from '@/api/system/checkType';
const props = defineProps({ const props = defineProps({
patientInfo: { type: Object, default: () => ({}) }, patientInfo: { type: Object, default: () => ({}) },
@@ -373,10 +381,69 @@ const categoryList = ref([]); // 原始分类+项目数据
const dictSearchKey = ref(''); const dictSearchKey = ref('');
const activeNames = ref([]); // 当前展开的折叠项 const activeNames = ref([]); // 当前展开的折叠项
const allMethods = ref([]);
// 加载所有检查方法
async function loadAllMethods() {
try {
const res = await listCheckMethod(); // 使用已导入的或者直接利用 request 请求
let methods = [];
if (res && res.data) {
if (Array.isArray(res.data)) {
methods = res.data;
} else if (res.data.records) {
methods = res.data.records;
} else if (res.data.data && Array.isArray(res.data.data)) {
methods = res.data.data;
}
} else if (Array.isArray(res)) {
methods = res;
} else if (res && res.rows) {
methods = res.rows;
}
allMethods.value = methods;
} catch (err) {
console.error('加载检查方法失败', err);
}
}
onMounted(async () => { onMounted(async () => {
await loadAllMethods();
await loadCategoryList(); await loadCategoryList();
}); });
// 动态可用的检查方法(根据已选部位所属的检查类型进行过滤)
const normalizeTypeValue = value => String(value ?? '').trim().toLowerCase();
const availableMethods = computed(() => {
// 获取当前已选部位的检查类型(可取第一个选中的部位的 checkType
const currentType = form.examTypeCode || (selectedItems.value.length > 0 ? selectedItems.value[0].checkType : '');
const normalizedCurrentType = normalizeTypeValue(currentType);
if (normalizedCurrentType) {
// 兼容脏数据method 的类型可能落在 checkType/type/typeCode/code/typeName/categoryName 中
const filtered = allMethods.value.filter(m => {
const typeCandidates = [
m.checkType,
m.type,
m.typeCode,
m.code,
m.typeName,
m.categoryName
].map(normalizeTypeValue).filter(Boolean);
return typeCandidates.includes(normalizedCurrentType);
});
return filtered.length > 0 ? filtered : allMethods.value;
}
return allMethods.value;
});
// 当可选方法列表改变时,如果当前选中的方法不在新列表中,则清空
watch(availableMethods, (newMethods) => {
if (form.inspectionMethod && !newMethods.find(m => m.name === form.inspectionMethod)) {
form.inspectionMethod = '';
}
});
/** /**
* 加载检查类型(分类)和检查项目(部位/项目),按类型分组展示 * 加载检查类型(分类)和检查项目(部位/项目),按类型分组展示
*/ */
@@ -390,25 +457,45 @@ async function loadCategoryList() {
params: { pageNo: 1, pageSize: 500 } // 取全量分类数据 params: { pageNo: 1, pageSize: 500 } // 取全量分类数据
}); });
let types = []; let types = [];
if (typeRes.data?.records) types = typeRes.data.records; if (typeRes && typeRes.data) {
else if (Array.isArray(typeRes.data)) types = typeRes.data; if (Array.isArray(typeRes.data)) {
else if (Array.isArray(typeRes.rows)) types = typeRes.rows; types = typeRes.data;
} else if (typeRes.data.records) {
types = typeRes.data.records;
} else if (typeRes.data.data && Array.isArray(typeRes.data.data)) {
types = typeRes.data.data;
}
} else if (Array.isArray(typeRes)) {
types = typeRes;
} else if (typeRes && typeRes.rows) {
types = typeRes.rows;
}
// 2. 加载检查项目(检查部位项目) // 2. 加载检查项目(检查部位项目)
const partRes = await request({ url: '/check/part/list', method: 'get' }); const partRes = await request({ url: '/check/part/list', method: 'get' });
let parts = []; let parts = [];
if (Array.isArray(partRes)) parts = partRes; if (partRes && partRes.data) {
else if (Array.isArray(partRes.data?.data)) parts = partRes.data.data; // 双层嵌套:{ data: { data: [...] } } if (Array.isArray(partRes.data)) {
else if (Array.isArray(partRes.data)) parts = partRes.data; parts = partRes.data;
else if (Array.isArray(partRes.rows)) parts = partRes.rows; } else if (partRes.data.records) {
else if (partRes.data?.records) parts = partRes.data.records; parts = partRes.data.records;
} else if (partRes.data.data && Array.isArray(partRes.data.data)) {
parts = partRes.data.data;
}
} else if (Array.isArray(partRes)) {
parts = partRes;
} else if (partRes && partRes.rows) {
parts = partRes.rows;
}
// 3. 按 checkType 归类 // 3. 按 checkType 归类
const dict = []; const dict = [];
for (const t of types) { for (const t of types) {
dict.push({ dict.push({
typeId: t.id, typeId: t.id,
typeCode: t.type, typeCode: t.code, // 保存 code 用于后备匹配
orgType: t.type, // 保存 type 用于后备匹配
typeName: t.name, // 保存 name
categoryName: t.name, categoryName: t.name,
items: [] items: []
}); });
@@ -425,7 +512,15 @@ async function loadCategoryList() {
nationalCode: p.nationalCode || '', nationalCode: p.nationalCode || '',
checked: false checked: false
}; };
const target = dict.find(d => d.typeCode === p.checkType);
// 增强匹配逻辑:部位的 checkType (如 'ECG', 'CT') 优先去匹配大类的 orgType
// 如果大类的 type 字段脏了(比如填了中文),则尝试匹配 code甚至是分类名称
const target = dict.find(d =>
d.orgType === p.checkType ||
d.typeCode === p.checkType ||
d.typeName === p.checkType
);
if (target) target.items.push(mapped); if (target) target.items.push(mapped);
else unclassified.push(mapped); else unclassified.push(mapped);
} }

View File

@@ -140,6 +140,12 @@ function handleReceive(row) {
// } // }
receiveEncounter(row.encounterId).then(() => { receiveEncounter(row.encounterId).then(() => {
emits('toCurrent', row); emits('toCurrent', row);
}).catch(error => {
// 如果接诊失败,检查是否是"已接诊"的错误
if (error && error.message && error.message.includes('已接诊')) {
// 自动刷新列表,移除已接诊的患者
getPatientList();
}
}); });
} }

View File

@@ -191,12 +191,18 @@
<template v-if="scope.row.adviceType == 1"> <template v-if="scope.row.adviceType == 1">
<div style="display: flex; align-items: center; margin-bottom: 16px; gap: 16px"> <div style="display: flex; align-items: center; margin-bottom: 16px; gap: 16px">
<span class="medicine-title"> <span class="medicine-title">
{{ scope.row.adviceName }} {{ scope.row.volume }} {{
<template v-if="scope.row.partPercent !== null && scope.row.partPercent !== undefined && scope.row.partPercent - 1 > 0"> scope.row.adviceName +
[1{{ scope.row.unitCode_dictText }}={{ scope.row.partPercent }}{{ scope.row.minUnitCode_dictText }}] ' ' +
</template> scope.row.volume +
[{{ (scope.row.unitPrice !== undefined && scope.row.unitPrice !== null && !isNaN(scope.row.unitPrice) && ' [' +
isFinite(scope.row.unitPrice)) ? Number(scope.row.unitPrice).toFixed(2) : '-' }} /{{ scope.row.unitCode_dictText }}] (scope.row.unitPrice !== undefined && scope.row.unitPrice !== null && !isNaN(scope.row.unitPrice) &&
isFinite(scope.row.unitPrice) ? Number(scope.row.unitPrice).toFixed(2) : '-') +
' 元' +
'/' +
scope.row.unitCode_dictText +
']'
}}
</span> </span>
<el-form-item prop="lotNumber" label="药房:"> <el-form-item prop="lotNumber" label="药房:">
<el-select v-model="scope.row.inventoryId" style="width: 330px; margin-right: 20px" <el-select v-model="scope.row.inventoryId" style="width: 330px; margin-right: 20px"
@@ -261,7 +267,6 @@
<div class="form-group"> <div class="form-group">
<el-form-item label="给药途径:" prop="methodCode" class="required-field" data-prop="methodCode"> <el-form-item label="给药途径:" prop="methodCode" class="required-field" data-prop="methodCode">
<el-select v-model="scope.row.methodCode" placeholder="给药途径" clearable filterable <el-select v-model="scope.row.methodCode" placeholder="给药途径" clearable filterable
style="width: 150px"
:ref="(el) => (inputRefs.methodCode = el)" @keyup.enter.prevent=" :ref="(el) => (inputRefs.methodCode = el)" @keyup.enter.prevent="
() => { () => {
if (scope.row.methodCode) { if (scope.row.methodCode) {
@@ -284,9 +289,7 @@
} }
// inputRefs.rateCode.blur(); // inputRefs.rateCode.blur();
} }
" " :ref="(el) => (inputRefs.rateCode = el)">
@change="calculateTotalAmount(scope.row, scope.$index)"
:ref="(el) => (inputRefs.rateCode = el)">
<el-option v-for="dict in rate_code" @click="() => (scope.row.rateCode_dictText = dict.label)" <el-option v-for="dict in rate_code" @click="() => (scope.row.rateCode_dictText = dict.label)"
:key="dict.value" :label="dict.label" :value="dict.value" /> :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select> </el-select>
@@ -307,10 +310,7 @@
controls-position="right" :controls="false" :ref="(el) => (inputRefs.dispensePerDuration = el)" controls-position="right" :controls="false" :ref="(el) => (inputRefs.dispensePerDuration = el)"
@keyup.enter.prevent=" @keyup.enter.prevent="
handleEnter('dispensePerDuration', scope.row, scope.$index) handleEnter('dispensePerDuration', scope.row, scope.$index)
" ">
@change="calculateTotalAmount(scope.row, scope.$index)"
@blur="calculateTotalAmount(scope.row, scope.$index)"
@input="calculateTotalAmount(scope.row, scope.$index)">
<template #suffix></template> <template #suffix></template>
</el-input-number> </el-input-number>
</el-form-item> </el-form-item>
@@ -321,8 +321,8 @@
@keyup.enter.prevent="handleEnter('quantity', scope.row, scope.$index)" @keyup.enter.prevent="handleEnter('quantity', scope.row, scope.$index)"
@input="calculateTotalPrice(scope.row, scope.$index)" /> @input="calculateTotalPrice(scope.row, scope.$index)" />
</el-form-item> </el-form-item>
<el-form-item style="margin-left: -10px;"> <el-form-item>
<el-select v-model="scope.row.unitCode" style="width: 70px; margin-right: 10px" placeholder=" " <el-select v-model="scope.row.unitCode" style="width: 70px; margin-right: 20px" placeholder=" "
@change="calculateTotalAmount(scope.row, scope.$index)"> @change="calculateTotalAmount(scope.row, scope.$index)">
<template v-for="item in scope.row.unitCodeList" :key="item.value"> <template v-for="item in scope.row.unitCodeList" :key="item.value">
<el-option v-if="checkUnit(item, scope.row)" :value="item.value" :label="item.label" @click=" <el-option v-if="checkUnit(item, scope.row)" :value="item.value" :label="item.label" @click="
@@ -342,13 +342,7 @@
" /> " />
</template> </template>
</el-select> </el-select>
<span v-if="scope.row.unitCode_dictText" class="unit-text">{{ scope.row.unitCode_dictText }}</span>
</el-form-item> </el-form-item>
<!-- 🔧 Bug #273 拆零比提示 -->
<span v-if="scope.row.partPercent !== null && scope.row.partPercent !== undefined && scope.row.partPercent - 1 > 0 && scope.row.unitCode !== scope.row.minUnitCode"
class="part-percent-hint">
{{ scope.row.partPercent }}<template v-if="scope.row.minUnitCode_dictText">{{ scope.row.minUnitCode_dictText }}</template><template v-else>袋</template>/<template v-if="scope.row.unitCode_dictText">{{ scope.row.unitCode_dictText }}</template><template v-else>盒</template>
</span>
</div> </div>
</div> </div>
<div style=" <div style="
@@ -552,6 +546,11 @@
expandOrder = []; expandOrder = [];
// 当医嘱类型改变时,清空当前选择的项目名称,因为不同类型项目的数据结构可能不兼容 // 当医嘱类型改变时,清空当前选择的项目名称,因为不同类型项目的数据结构可能不兼容
prescriptionList[scope.$index].adviceName = undefined; prescriptionList[scope.$index].adviceName = undefined;
prescriptionList[scope.$index].adviceType_dictText = '';
// 🔧 Bug Fix: 医嘱类型改变时重置minUnitQuantity和minUnitCode避免null值
prescriptionList[scope.$index].minUnitQuantity = prescriptionList[scope.$index].quantity || 1;
prescriptionList[scope.$index].minUnitCode = prescriptionList[scope.$index].unitCode;
prescriptionList[scope.$index].minUnitCode_dictText = prescriptionList[scope.$index].unitCode_dictText;
adviceQueryParams.adviceTypes = value; // 🎯 修复:改为 adviceTypes复数 adviceQueryParams.adviceTypes = value; // 🎯 修复:改为 adviceTypes复数
// 根据选择的类型设置categoryCode用于药品分类筛选 // 根据选择的类型设置categoryCode用于药品分类筛选
@@ -687,6 +686,7 @@
style="width: 60px" style="width: 60px"
size="small" size="small"
@change="calculateTotalPrice(scope.row, scope.$index)" @change="calculateTotalPrice(scope.row, scope.$index)"
@input="calculateTotalPrice(scope.row, scope.$index)"
/> />
<span style="margin-left: 4px">{{ scope.row.unitCode_dictText }}</span> <span style="margin-left: 4px">{{ scope.row.unitCode_dictText }}</span>
</template> </template>
@@ -734,7 +734,7 @@
size="small" size="small"
/> />
<span style="margin: 0 2px; font-size: 12px;">天</span> <span style="margin: 0 2px; font-size: 12px;">天</span>
<el-select v-model="scope.row.methodCode" size="small" style="width: 120px" placeholder="用法"> <el-select v-model="scope.row.methodCode" size="small" style="width: 65px" placeholder="用法">
<el-option v-for="item in method_code" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in method_code" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</template> </template>
@@ -957,37 +957,42 @@ const { method_code, unit_code, rate_code, distribution_category_code, drord_doc
'drord_doctor_type' 'drord_doctor_type'
); );
// 删除硬编码的adviceTypeList直接使用drord_doctor_type字典 // 使用 drord_doctor_type 字典,不再硬编码
// drord_doctor_type: 1=西药, 2=中成药, 3=诊疗, 4=耗材, 5=会诊, 6=全部 // drord_doctor_type: 1=西药, 2=中成药, 3=诊疗, 4=耗材, 5=会诊, 6=手术
const adviceTypeList = ref([ const adviceTypeList = computed(() => {
{ // 如果字典已加载,使用字典数据;否则使用默认值
label: '西药', let list = [];
value: 1, if (drord_doctor_type.value && drord_doctor_type.value.length > 0) {
}, // 过滤掉字典中已有的"全部"选项,避免重复
{ list = drord_doctor_type.value
label: '中成药', .filter(item => item.label !== '全部')
value: 2, .map(item => ({
}, label: item.label,
{ value: parseInt(item.value) || item.value
label: '诊疗', }));
value: 3, } else {
}, // 默认返回值,确保页面正常显示
{ list = [
label: '耗材', { label: '西药', value: 1 },
value: 4, { label: '中成药', value: 2 },
}, { label: '诊疗', value: 3 },
{ { label: '耗材', value: 4 },
label: '会诊', { label: '会诊', value: 5 },
value: 5, { label: '手术', value: 6 },
}, ];
{ }
label: '全部', // 在最后添加"全部"选项
value: '', list.push({ label: '全部', value: 0 });
}, return list;
]); });
// 根据类型值获取显示标签,避免非编辑态出现空标签 // 根据类型值获取显示标签,避免非编辑态出现空标签
const mapAdviceTypeLabel = (type) => { const mapAdviceTypeLabel = (type, adviceTableName) => {
// 🔧 Bug Fix: 根据adviceTableName判断耗材类型
// 后端adviceType=2既表示中成药又表示耗材需要通过表名区分
if (type === 2 && adviceTableName === 'adm_device_definition') {
return '耗材';
}
const found = adviceTypeList.value.find((item) => item.value === type); const found = adviceTypeList.value.find((item) => item.value === type);
return found ? found.label : ''; return found ? found.label : '';
}; };
@@ -1588,11 +1593,6 @@ function getListInfo(addNewRow) {
console.log('BugFix#219: 过滤掉已作废的会诊医嘱, requestId=', item.requestId); console.log('BugFix#219: 过滤掉已作废的会诊医嘱, requestId=', item.requestId);
return false; return false;
} }
// 🔧 Bug Fix: 过滤掉项目名称为空的无效医嘱
if (!item.adviceName || item.adviceName.trim() === '') {
console.log('BugFix: 过滤掉空白医嘱, requestId=', item.requestId, 'adviceType=', item.adviceType);
return false;
}
return true; return true;
}); });
@@ -1620,11 +1620,8 @@ function getListInfo(addNewRow) {
// 🔧 Bug Fix: 后端保存时将耗材(4)转换为中成药(2),显示时需要转换回来 // 🔧 Bug Fix: 后端保存时将耗材(4)转换为中成药(2),显示时需要转换回来
// 检查 adviceTableName如果是耗材表则应该是耗材类型 // 检查 adviceTableName如果是耗材表则应该是耗材类型
const adviceTableName = contentJson?.adviceTableName || item.adviceTableName; const adviceTableName = contentJson?.adviceTableName || item.adviceTableName;
if (adviceType === 2 && adviceTableName === 'adm_device_definition') {
adviceType = 4; // 后端2(中成药) -> 前端4(耗材)
}
let adviceType_dictText = item.adviceType_dictText || mapAdviceTypeLabel(adviceType); let adviceType_dictText = item.adviceType_dictText || mapAdviceTypeLabel(adviceType, adviceTableName);
// 如果是会诊类型,设置为会诊类型 // 如果是会诊类型,设置为会诊类型
if (isConsultation) { if (isConsultation) {
@@ -2036,7 +2033,6 @@ function handleDelete() {
adviceType: item.adviceType, adviceType: item.adviceType,
statusEnum: item.statusEnum, statusEnum: item.statusEnum,
requestId: item.requestId, requestId: item.requestId,
adviceName: item.adviceName
}))); })));
// 🔧 BugFix: 放宽条件只要有requestId的会诊医嘱都可以尝试删除 // 🔧 BugFix: 放宽条件只要有requestId的会诊医嘱都可以尝试删除
@@ -2141,18 +2137,13 @@ function handleDelete() {
adviceType: item.adviceType, adviceType: item.adviceType,
statusEnum: item.statusEnum, statusEnum: item.statusEnum,
requestId: item.requestId, requestId: item.requestId,
adviceName: item.adviceName,
uniqueKey: item.uniqueKey uniqueKey: item.uniqueKey
}))); })));
console.log('BugFix#219: prescriptionList 中的所有医嘱=', prescriptionList.value.map(item => ({ console.log('BugFix#219: prescriptionList 中的所有医嘱=', prescriptionList.value.map(item => ({
adviceType: item.adviceType, adviceType: item.adviceType,
statusEnum: item.statusEnum, statusEnum: item.statusEnum,
adviceName: item.adviceName, uniqueKey: item.uniqueKey
uniqueKey: item.uniqueKey,
totalPrice: item.totalPrice,
dose: item.dose,
doseQuantity: item.doseQuantity
}))); })));
for (let i = prescriptionList.value.length - 1; i >= 0; i--) { for (let i = prescriptionList.value.length - 1; i >= 0; i--) {
@@ -2341,10 +2332,14 @@ function handleSave(prescriptionId) {
// 签发核心逻辑 // 签发核心逻辑
function executeSaveLogic() { function executeSaveLogic() {
// 🔧 Bug Fix: 获取当前选中的费用性质,保持字符串类型避免大整数精度丢失 // 🔧 Bug Fix: 获取当前选中的费用性质,如果是'ZIFEI'或0则转为null让后端查询默认账户
let finalAccountId = accountId.value; let finalAccountId = accountId.value;
if (finalAccountId === 'ZIFEI' || finalAccountId === 0) { if (finalAccountId === 'ZIFEI' || finalAccountId === 0) {
finalAccountId = null; finalAccountId = null;
} else if (finalAccountId && !isNaN(Number(finalAccountId))) {
finalAccountId = Number(finalAccountId);
} else {
finalAccountId = null;
} }
// 🔧 Bug Fix: 校验患者信息完整性 // 🔧 Bug Fix: 校验患者信息完整性
@@ -2357,8 +2352,19 @@ function handleSave(prescriptionId) {
saveList.forEach((item) => { saveList.forEach((item) => {
item.patientId = props.patientInfo.patientId; item.patientId = props.patientInfo.patientId;
item.encounterId = props.patientInfo.encounterId; item.encounterId = props.patientInfo.encounterId;
// 🔧 使用项目自己的 accountId而不是全局的 finalAccountId
// 这样自动带出的耗材才能保持与药品相同的账户
if (!item.accountId) {
item.accountId = finalAccountId; item.accountId = finalAccountId;
}
item.dbOpType = '1'; item.dbOpType = '1';
// 🔧 Bug Fix: 确保耗材的minUnitQuantity被正确设置
if (item.adviceType == 4) {
item.minUnitQuantity = item.quantity;
item.minUnitCode = item.unitCode;
item.minUnitCode_dictText = item.unitCode_dictText;
}
}); });
loading.value = true; loading.value = true;
@@ -2367,7 +2373,7 @@ function handleSave(prescriptionId) {
// 先解析contentJson // 先解析contentJson
let parsedContent = JSON.parse(item.contentJson || '{}'); let parsedContent = JSON.parse(item.contentJson || '{}');
// 🔧 Bug Fix: 强制将accountId设为正确的值 // 🔧 Bug Fix: 强制将accountId设为正确的值
parsedContent.accountId = finalAccountId; parsedContent.accountId = item.accountId || finalAccountId; // 🔧 使用 item 自己的 accountId
// --- 【修改点2Bug 2 修复 (单位换算)】 --- // --- 【修改点2Bug 2 修复 (单位换算)】 ---
let finalQuantity = item.quantity; let finalQuantity = item.quantity;
@@ -2382,17 +2388,24 @@ function handleSave(prescriptionId) {
finalUnitCode = item.minUnitCode; finalUnitCode = item.minUnitCode;
} }
item.minUnitQuantity = finalQuantity; item.minUnitQuantity = finalQuantity;
} else if (item.adviceType == 4) {
// 🔧 Bug Fix: 耗材类型只有一个单位minUnitQuantity等于quantity
item.minUnitQuantity = item.quantity;
// 🔧 Bug Fix: 确保minUnitCode等于unitCode
item.minUnitCode = item.unitCode;
item.minUnitCode_dictText = item.unitCode_dictText;
finalUnitCode = item.unitCode;
} else { } else {
item.minUnitQuantity = item.quantity; item.minUnitQuantity = item.quantity;
} }
// --- 【修改点3Bug 1 修复 (类型转换)】 --- // --- 【修改点3Bug 1 修复 (类型转换)】 ---
let saveAdviceType = item.adviceType; let saveAdviceType = item.adviceType;
if (item.adviceType == 4) { // 🔧 Bug Fix: 保持原类型,不进行转换
saveAdviceType = 2; // 耗材前端4 -> 后端2 // 前端类型: 1=西药, 2=中成药, 3=诊疗, 4=耗材, 5=会诊, 6=手术
} else if (item.adviceType == 2) { // 后端类型: 1=药品, 2=耗材, 3=医疗活动, 6=手术
saveAdviceType = 1; // 中成药前端2 -> 后端1 // 后端会通过 ItemType.DEVICE.getValue() || adviceType == 4 来识别耗材
} else if (item.adviceType == 5) { if (item.adviceType == 5) {
saveAdviceType = 3; // 会诊前端5 -> 后端3诊疗类 saveAdviceType = 3; // 会诊前端5 -> 后端3诊疗类
} }
@@ -2403,6 +2416,21 @@ function handleSave(prescriptionId) {
console.warn('Fixed NaN totalPrice for item:', item.adviceName); console.warn('Fixed NaN totalPrice for item:', item.adviceName);
} }
// 🔧 BugFix#318: 从 parsedContent 提取标准医嘱字段,排除手术特有字段
const standardFields = [
'accountId', 'chargeItemId', 'conditionDefinitionId', 'conditionId',
'contentJson', 'definitionDetailId', 'definitionId', 'diagnosisName',
'dosageInstruction', 'effectiveOrgId', 'encounterDiagnosisId',
'encounterId', 'lotNumber', 'patientId', 'practitionerId',
'prescriptionNo', 'skinTestFlag', 'unitPrice', 'volume', 'ybClassEnum'
];
let filteredContent = {};
standardFields.forEach(field => {
if (parsedContent[field] !== undefined) {
filteredContent[field] = parsedContent[field];
}
});
// 构造请求参数 // 构造请求参数
// 🔧 Bug Fix: 确保库存匹配成功的关键字段 // 🔧 Bug Fix: 确保库存匹配成功的关键字段
// 耗材使用 adm_device_definition 表 // 耗材使用 adm_device_definition 表
@@ -2417,7 +2445,6 @@ function handleSave(prescriptionId) {
} }
console.log('保存医嘱参数:', { console.log('保存医嘱参数:', {
adviceName: item.adviceName,
adviceType: item.adviceType, adviceType: item.adviceType,
saveAdviceType: saveAdviceType, saveAdviceType: saveAdviceType,
adviceTableName: adviceTableNameVal, adviceTableName: adviceTableNameVal,
@@ -2427,29 +2454,53 @@ function handleSave(prescriptionId) {
}); });
return { return {
...parsedContent, ...filteredContent, // 🔧 BugFix#318: 使用过滤后的字段,排除手术特有字段
adviceType: saveAdviceType, // 使用转换后的类型 adviceType: saveAdviceType, // 使用转换后的类型
requestId: item.requestId, requestId: item.requestId,
dbOpType: '1', dbOpType: item.requestId ? '2' : '1', // 🔧 BugFix: 根据requestId判断是新增还是修改
encounterId: item.encounterId || props.patientInfo.encounterId, // 🔧 BugFix: 确保encounterId
patientId: item.patientId || props.patientInfo.patientId, // 🔧 BugFix: 确保patientId
groupId: item.groupId, groupId: item.groupId,
uniqueKey: undefined, uniqueKey: undefined,
// 使用转换后的数量和单位 // 使用转换后的数量和单位
quantity: finalQuantity, quantity: finalQuantity,
unitCode: finalUnitCode, unitCode: finalUnitCode,
totalPrice: item.totalPrice, // Ensure totalPrice is valid totalPrice: item.totalPrice, // Ensure totalPrice is valid
// 🔧 Bug Fix: 确保 categoryEnum 被传递(耗材必填字段)
categoryEnum: item.categoryEnum || parsedContent.categoryEnum,
// 🔧 Bug Fix: 确保库存匹配成功的关键字段 // 🔧 Bug Fix: 确保库存匹配成功的关键字段
adviceTableName: adviceTableNameVal, adviceTableName: adviceTableNameVal,
locationId: locationIdVal, locationId: locationIdVal,
// 🔧 确保 methodCode 被传递(用于触发耗材绑定逻辑) // 🔧 确保 methodCode 被传递(用于触发耗材绑定逻辑)
methodCode: item.methodCode || parsedContent.methodCode methodCode: item.methodCode || parsedContent.methodCode,
// 🔧 确保 accountId 被传递(用于预结算)
accountId: item.accountId || parsedContent.accountId,
// 🔧 Bug Fix: 确保minUnitQuantity被传递耗材必填字段
minUnitQuantity: item.minUnitQuantity,
minUnitCode: item.minUnitCode,
minUnitCode_dictText: item.minUnitCode_dictText,
// 🔧 Bug Fix: 确保 definitionId 和 definitionDetailId 被传递(费用项必填字段)
definitionId: item.definitionId || parsedContent.definitionId,
definitionDetailId: item.definitionDetailId || parsedContent.definitionDetailId,
// 🔧 更新 contentJson 中的 adviceType确保后端分类正确
contentJson: JSON.stringify({
...parsedContent,
adviceType: saveAdviceType
})
}; };
}); });
// 提交签发请求 // 提交签发请求
isSaving.value = true; isSaving.value = true;
console.log('签发处方参数:', { console.log('organizationId:', props.patientInfo.orgId);
organizationId: props.patientInfo.orgId, console.log('adviceSaveList:', list);
adviceSaveList: list, list.forEach((item, idx) => {
console.log(`项目${idx + 1}:`, {
adviceName: item.adviceName,
adviceType: item.adviceType,
accountId: item.accountId,
methodCode: item.methodCode
});
}); });
savePrescriptionSign({ savePrescriptionSign({
@@ -2602,7 +2653,8 @@ function handleOrderBindInfo(bindIdInfo, currentMethodCode) {
uniqueKey: nextId.value++, uniqueKey: nextId.value++,
patientId: props.patientInfo.patientId, patientId: props.patientInfo.patientId,
encounterId: props.patientInfo.encounterId, encounterId: props.patientInfo.encounterId,
accountId: accountId.value, accountId: accountId.value, // 🔧 使用当前全局的 accountId
requestId: undefined, // 🔧 耗材的 requestId 设为 undefined让后端自动生成
quantity: item.quantity, quantity: item.quantity,
methodCode: item.methodCode, // 🔧 现在 item.methodCode 有值了 methodCode: item.methodCode, // 🔧 现在 item.methodCode 有值了
rateCode: item.rateCode, rateCode: item.rateCode,
@@ -2613,18 +2665,23 @@ function handleOrderBindInfo(bindIdInfo, currentMethodCode) {
unitCode: item.unitCode, unitCode: item.unitCode,
unitCode_dictText: item.unitCodeName || '', unitCode_dictText: item.unitCodeName || '',
statusEnum: 1, statusEnum: 1,
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1', dbOpType: '1', // 🔧 新耗材总是 INSERT
conditionId: conditionId.value, conditionId: conditionId.value,
conditionDefinitionId: conditionDefinitionId.value, conditionDefinitionId: conditionDefinitionId.value,
encounterDiagnosisId: encounterDiagnosisId.value, encounterDiagnosisId: encounterDiagnosisId.value,
// 🔧 确保 adviceType 和显示文本正确设置 // 🔧 确保 adviceType 和显示文本正确设置
adviceType: item.adviceType, adviceType: item.adviceType,
adviceType_dictText: mapAdviceTypeLabel(item.adviceType), adviceType_dictText: mapAdviceTypeLabel(item.adviceType, item.adviceTableName),
}; };
// 计算价格和总量 // 计算价格和总量
const unitInfo = unitCodeList.value.find((k) => k.value == item.unitCode); const unitInfo = unitCodeList.value.find((k) => k.value == item.unitCode);
if (unitInfo && unitInfo.type == 'minUnit') { // 🔧 Bug Fix: 耗材类型只有一个单位minUnitQuantity等于quantity
if (item.adviceType == 4) {
newRow.price = newRow.unitPrice;
newRow.totalPrice = (item.quantity * newRow.unitPrice).toFixed(6);
newRow.minUnitQuantity = item.quantity;
} else if (unitInfo && unitInfo.type == 'minUnit') {
newRow.price = newRow.minUnitPrice; newRow.price = newRow.minUnitPrice;
newRow.totalPrice = (item.quantity * newRow.minUnitPrice).toFixed(6); newRow.totalPrice = (item.quantity * newRow.minUnitPrice).toFixed(6);
newRow.minUnitQuantity = item.quantity; newRow.minUnitQuantity = item.quantity;
@@ -2689,6 +2746,12 @@ function handleSaveSign(row, index, prescriptionId) {
return; return;
} }
// 🔧 Bug Fix: 验证医嘱类型不能为"全部"
if (row.adviceType === 0) {
proxy.$modal.msgWarning('请选择医嘱类型');
return;
}
// 重新查找索引,确保使用当前处方列表中的正确索引 // 重新查找索引,确保使用当前处方列表中的正确索引
const actualIndex = prescriptionList.value.findIndex(item => item.uniqueKey === row.uniqueKey); const actualIndex = prescriptionList.value.findIndex(item => item.uniqueKey === row.uniqueKey);
if (actualIndex === -1) { if (actualIndex === -1) {
@@ -2723,11 +2786,14 @@ function handleSaveSign(row, index, prescriptionId) {
formRef.validate((valid) => { formRef.validate((valid) => {
if (valid) { if (valid) {
if (row.adviceType != 2) { // 🔧 BugFix#318: 手术类型(adviceType=6)不需要检查绑定耗材/药品
if (row.adviceType != 2 && row.adviceType != 6) {
// 1:用法绑东西 2:诊疗绑东西 // 1:用法绑东西 2:诊疗绑东西
let typeCode = row.adviceType == 1 ? '1' : '2'; let typeCode = row.adviceType == 1 ? '1' : '2';
// 用法字典值/诊疗定义id // 用法字典值/诊疗定义id
let itemNo = row.adviceType == 1 ? row.methodCode : row.adviceDefinitionId; let itemNo = row.adviceType == 1 ? row.methodCode : row.adviceDefinitionId;
// 🔧 确保 itemNo 有值才调用接口
if (itemNo) {
getBindDevice({ typeCode: typeCode, itemNo: itemNo }).then((res) => { getBindDevice({ typeCode: typeCode, itemNo: itemNo }).then((res) => {
if (res.data.length == 0) { if (res.data.length == 0) {
return; return;
@@ -2746,6 +2812,7 @@ function handleSaveSign(row, index, prescriptionId) {
} }
}); });
} }
}
row.isEdit = false; row.isEdit = false;
isAdding.value = false; isAdding.value = false;
updateExpandOrder([]); updateExpandOrder([]);
@@ -2754,28 +2821,37 @@ function handleSaveSign(row, index, prescriptionId) {
row.encounterId = props.patientInfo.encounterId; row.encounterId = props.patientInfo.encounterId;
row.accountId = accountId.value; row.accountId = accountId.value;
// 确保非编辑态显示正确的医嘱类型标签 // 确保非编辑态显示正确的医嘱类型标签
row.adviceType_dictText = mapAdviceTypeLabel(row.adviceType); row.adviceType_dictText = mapAdviceTypeLabel(row.adviceType, row.adviceTableName);
// 更新本地显示的最小单位数量(仅用于前端逻辑,不影响显示单位) // 更新本地显示的最小单位数量(仅用于前端逻辑,不影响显示单位)
if (row.adviceType == 1 || row.adviceType == 2) { if (row.adviceType == 1 || row.adviceType == 2) {
row.minUnitQuantity = row.minUnitQuantity =
row.minUnitCode == row.unitCode ? row.quantity : row.quantity * row.partPercent; row.minUnitCode == row.unitCode ? row.quantity : row.quantity * row.partPercent;
} else { } else {
// 🔧 Bug Fix: 耗材和其他类型minUnitQuantity等于quantity
row.minUnitQuantity = row.quantity; row.minUnitQuantity = row.quantity;
// 🔧 Bug Fix: 确保minUnitCode等于unitCode耗材只有一个单位
row.minUnitCode = row.unitCode;
row.minUnitCode_dictText = row.unitCode_dictText;
} }
row.conditionId = conditionId.value; row.conditionId = conditionId.value;
// 处理总量为小单位情况,需要把单价也保存成小单位的 // 处理总量为小单位情况,需要把单价也保存成小单位的
if (row.unitCodeList.find((item) => item.value == row.unitCode).type == 'unit') { // 🔧 BugFix#318: 手术类型(adviceType=6)可能没有unitCodeList需要判断
if (row.unitCodeList && row.unitCodeList.length > 0) {
const foundUnit = row.unitCodeList.find((item) => item.value == row.unitCode);
if (foundUnit && foundUnit.type == 'unit') {
if (row.adviceType != 3) { if (row.adviceType != 3) {
row.unitPrice = row.unitTempPrice; row.unitPrice = row.unitTempPrice;
} }
} else { } else {
row.unitCode_dictText = row.unitCodeList.find( const minUnitItem = row.unitCodeList.find((item) => item.value == row.minUnitCode);
(item) => item.value == row.minUnitCode if (minUnitItem) {
).label; row.unitCode_dictText = minUnitItem.label;
}
row.unitPrice = row.minUnitPrice; row.unitPrice = row.minUnitPrice;
} }
}
row.conditionDefinitionId = conditionDefinitionId.value; row.conditionDefinitionId = conditionDefinitionId.value;
row.encounterDiagnosisId = encounterDiagnosisId.value; row.encounterDiagnosisId = encounterDiagnosisId.value;
row.diagnosisName = diagnosisName.value; row.diagnosisName = diagnosisName.value;
@@ -2882,10 +2958,11 @@ function handleSaveBatch(prescriptionId) {
} }
// 🔧 Bug Fix: 在保存时才转换 accountId // 🔧 Bug Fix: 在保存时才转换 accountId
// 保持为字符串类型,避免 JavaScript 大整数精度丢失问题
let finalAccountId = accountId.value; let finalAccountId = accountId.value;
if (finalAccountId === 'ZIFEI' || finalAccountId === 0) { if (finalAccountId === 'ZIFEI') {
finalAccountId = null; finalAccountId = null;
} else if (finalAccountId && !isNaN(Number(finalAccountId))) {
finalAccountId = Number(finalAccountId);
} }
// 更新到处方对象 // 更新到处方对象
@@ -2950,14 +3027,35 @@ function handleSaveBatch(prescriptionId) {
// --- Bug 1 修复:类型转换 --- // --- Bug 1 修复:类型转换 ---
let saveAdviceType = item.adviceType; let saveAdviceType = item.adviceType;
if (item.adviceType == 4) { // 🔧 Bug Fix: 保持原类型,不进行转换
saveAdviceType = 2; // 耗材前端4 -> 后端2 // 前端类型: 1=西药, 2=中成药, 3=诊疗, 4=耗材, 5=会诊, 6=手术
} else if (item.adviceType == 2) { // 后端类型: 1=药品, 2=耗材, 3=医疗活动, 6=手术
saveAdviceType = 1; // 中成药前端2 -> 后端1 // 后端会通过 ItemType.DEVICE.getValue() || adviceType == 4 来识别耗材
} else if (item.adviceType == 5) { if (item.adviceType == 5) {
saveAdviceType = 3; // 会诊前端5 -> 后端3诊疗类 saveAdviceType = 3; // 会诊前端5 -> 后端3诊疗类
} }
// 🔧 BugFix#318: 过滤掉手术特有字段,只保留标准医嘱字段
const standardItemFields = [
'adviceDefinitionId', 'adviceName', 'adviceTableName', 'adviceType',
'basedOnId', 'chargeItemId', 'chargeStatus', 'conditionDefinitionId',
'conditionId', 'contentJson', 'dose', 'doseUnitCode', 'encounterDiagnosisId',
'encounterId', 'groupId', 'injectFlag', 'lotNumber', 'methodCode', 'partPercent',
'patientId', 'positionId', 'positionName', 'prescriptionNo', 'quantity', 'rateCode',
'requestId', 'skinTestFlag', 'sortNumber', 'statusEnum', 'totalPrice',
'unitCode', 'unitPrice', 'volume', 'ybClassEnum',
// 🔧 Bug Fix: 添加 definitionId 和 definitionDetailId 字段
'definitionId', 'definitionDetailId',
// 🔧 Bug Fix: 添加 categoryEnum 字段(耗材必填)
'categoryEnum'
];
let filteredItem = {};
standardItemFields.forEach(field => {
if (item[field] !== undefined) {
filteredItem[field] = item[field];
}
});
// 构造 contentJson (保持前端UI原始数据) // 构造 contentJson (保持前端UI原始数据)
const itemToSave = { const itemToSave = {
...item, ...item,
@@ -2967,10 +3065,12 @@ function handleSaveBatch(prescriptionId) {
}; };
const contentJson = JSON.stringify(itemToSave); const contentJson = JSON.stringify(itemToSave);
// 🔧 Bug Fix: 处理accountId保持字符串类型避免大整数精度丢失 // 🔧 Bug Fix: 处理accountId如果是'ZIFEI'或0则转为null让后端查询默认账户
let itemAccountId = finalAccountId; let itemAccountId = finalAccountId;
if (itemAccountId === 'ZIFEI' || itemAccountId === 0) { if (itemAccountId === 'ZIFEI' || itemAccountId === 0) {
itemAccountId = null; itemAccountId = null;
} else if (itemAccountId && !isNaN(Number(itemAccountId))) {
itemAccountId = Number(itemAccountId);
} }
// 🔧 Bug Fix: 确保库存匹配成功的关键字段 // 🔧 Bug Fix: 确保库存匹配成功的关键字段
@@ -3003,7 +3103,7 @@ function handleSaveBatch(prescriptionId) {
}); });
return { return {
...item, ...filteredItem, // 🔧 BugFix#318: 使用过滤后的字段
patientId: props.patientInfo.patientId, patientId: props.patientInfo.patientId,
encounterId: props.patientInfo.encounterId, encounterId: props.patientInfo.encounterId,
adviceType: saveAdviceType, adviceType: saveAdviceType,
@@ -3144,14 +3244,16 @@ function syncGroupFields(row) {
} }
function setValue(row) { function setValue(row) {
// 🔧 Bug Fix: 强制设置耗材类型,确保 adviceType 为 4
// 如果 adviceTableName 是 adm_device_definition强制设为耗材类型
if (row.adviceTableName === 'adm_device_definition') {
row.adviceType = 4;
}
unitCodeList.value = []; unitCodeList.value = [];
unitCodeList.value.push({ value: row.unitCode, label: row.unitCode_dictText, type: 'unit' }); unitCodeList.value.push({ value: row.unitCode, label: row.unitCode_dictText, type: 'unit' });
// 🔧 Bug Fix: 耗材类型只有一个单位不需要dose和minUnit选项
if (row.adviceType == 4) {
// 耗材只添加一个单位选项
row.minUnitCode = row.unitCode;
row.minUnitCode_dictText = row.unitCode_dictText;
} else {
// 药品类型添加dose和minUnit选项
unitCodeList.value.push({ unitCodeList.value.push({
value: row.doseUnitCode, value: row.doseUnitCode,
label: row.doseUnitCode_dictText, label: row.doseUnitCode_dictText,
@@ -3163,6 +3265,7 @@ function setValue(row) {
label: row.minUnitCode_dictText, label: row.minUnitCode_dictText,
type: 'minUnit', type: 'minUnit',
}); });
}
if (row.adviceType == 2 && row.minUnitCode != row.unitCode) { if (row.adviceType == 2 && row.minUnitCode != row.unitCode) {
unitCodeList.value.push({ unitCodeList.value.push({
value: row.minUnitCode, value: row.minUnitCode,
@@ -3196,8 +3299,15 @@ function setValue(row) {
prescriptionList.value[rowIndex.value].unitCodeList = unitCodeList.value; prescriptionList.value[rowIndex.value].unitCodeList = unitCodeList.value;
prescriptionList.value[rowIndex.value].doseUnitCode = row.doseUnitCode; prescriptionList.value[rowIndex.value].doseUnitCode = row.doseUnitCode;
prescriptionList.value[rowIndex.value].minUnitCode = row.minUnitCode; prescriptionList.value[rowIndex.value].minUnitCode = row.minUnitCode;
// 🔧 Bug Fix: 耗材类型只有一个单位minUnitCode应该等于unitCode
if (Number(row.adviceType) == 4) {
prescriptionList.value[rowIndex.value].minUnitCode = row.unitCode;
prescriptionList.value[rowIndex.value].minUnitCode_dictText = row.unitCode_dictText;
prescriptionList.value[rowIndex.value].unitCode = row.unitCode;
} else {
prescriptionList.value[rowIndex.value].unitCode = prescriptionList.value[rowIndex.value].unitCode =
row.partAttributeEnum == 1 ? row.minUnitCode : row.unitCode; row.partAttributeEnum == 1 ? row.minUnitCode : row.unitCode;
}
prescriptionList.value[rowIndex.value].categoryEnum = row.categoryCode; prescriptionList.value[rowIndex.value].categoryEnum = row.categoryCode;
prescriptionList.value[rowIndex.value].skinTestFlag = row.skinTestFlag; prescriptionList.value[rowIndex.value].skinTestFlag = row.skinTestFlag;
prescriptionList.value[rowIndex.value].definitionId = row.chargeItemDefinitionId; prescriptionList.value[rowIndex.value].definitionId = row.chargeItemDefinitionId;
@@ -3266,6 +3376,10 @@ function setValue(row) {
// 🔧 Bug #218 修复保留组套中的quantity如果没有则默认1 // 🔧 Bug #218 修复保留组套中的quantity如果没有则默认1
prescriptionList.value[rowIndex.value].quantity = row.quantity || 1; prescriptionList.value[rowIndex.value].quantity = row.quantity || 1;
prescriptionList.value[rowIndex.value].totalPrice = validPrice * (row.quantity || 1); prescriptionList.value[rowIndex.value].totalPrice = validPrice * (row.quantity || 1);
// 🔧 Bug Fix: 设置耗材的minUnitQuantity避免保存时null错误
prescriptionList.value[rowIndex.value].minUnitQuantity = row.quantity || 1;
// 🔧 Bug Fix: 设置耗材的categoryEnum避免数据库约束错误
prescriptionList.value[rowIndex.value].categoryEnum = row.categoryCode || 3; // 默认为3
prescriptionList.value[rowIndex.value].positionName = row.positionName || ''; prescriptionList.value[rowIndex.value].positionName = row.positionName || '';
// 🔧 Bug Fix: 使用 positionId如果为空则使用患者信息中的 orgId // 🔧 Bug Fix: 使用 positionId如果为空则使用患者信息中的 orgId
console.log('设置耗材locationId:', { console.log('设置耗材locationId:', {
@@ -3284,6 +3398,10 @@ function setValue(row) {
prescriptionList.value[rowIndex.value].minUnitPrice = 0; prescriptionList.value[rowIndex.value].minUnitPrice = 0;
prescriptionList.value[rowIndex.value].quantity = row.quantity || 1; prescriptionList.value[rowIndex.value].quantity = row.quantity || 1;
prescriptionList.value[rowIndex.value].totalPrice = 0; prescriptionList.value[rowIndex.value].totalPrice = 0;
// 🔧 Bug Fix: 设置耗材的minUnitQuantity避免保存时null错误
prescriptionList.value[rowIndex.value].minUnitQuantity = row.quantity || 1;
// 🔧 Bug Fix: 设置耗材的categoryEnum避免数据库约束错误
prescriptionList.value[rowIndex.value].categoryEnum = row.categoryCode || 3; // 默认为3
prescriptionList.value[rowIndex.value].positionName = row.positionName || ''; prescriptionList.value[rowIndex.value].positionName = row.positionName || '';
const finalLocationId = row.positionId || props.patientInfo.orgId; const finalLocationId = row.positionId || props.patientInfo.orgId;
prescriptionList.value[rowIndex.value].locationId = finalLocationId; prescriptionList.value[rowIndex.value].locationId = finalLocationId;
@@ -3522,7 +3640,6 @@ function handleSingOut() {
let selectRows = prescriptionRef.value.getSelectionRows(); let selectRows = prescriptionRef.value.getSelectionRows();
console.log('BugFix#219: handleSingOut called, selectRows=', selectRows); console.log('BugFix#219: handleSingOut called, selectRows=', selectRows);
console.log('BugFix#219: 选中行详情:', selectRows.map(item => ({ console.log('BugFix#219: 选中行详情:', selectRows.map(item => ({
adviceName: item.adviceName,
adviceType: item.adviceType, adviceType: item.adviceType,
statusEnum: item.statusEnum, statusEnum: item.statusEnum,
statusEnumType: typeof item.statusEnum, statusEnumType: typeof item.statusEnum,
@@ -3540,7 +3657,6 @@ function handleSingOut() {
console.log('BugFix#219: consultationRows=', consultationRows.length, 'normalRows=', normalRows.length); console.log('BugFix#219: consultationRows=', consultationRows.length, 'normalRows=', normalRows.length);
console.log('BugFix#219: normalRows详情:', normalRows.map(item => ({ console.log('BugFix#219: normalRows详情:', normalRows.map(item => ({
adviceName: item.adviceName,
statusEnum: item.statusEnum, statusEnum: item.statusEnum,
statusEnumType: typeof item.statusEnum, statusEnumType: typeof item.statusEnum,
requestId: item.requestId requestId: item.requestId
@@ -3553,7 +3669,6 @@ function handleSingOut() {
adviceType: item.adviceType, adviceType: item.adviceType,
statusEnum: item.statusEnum, statusEnum: item.statusEnum,
requestId: item.requestId, requestId: item.requestId,
adviceName: item.adviceName
}))); })));
// 🔧 BugFix: 放宽条件只要有requestId的会诊医嘱都可以处理 // 🔧 BugFix: 放宽条件只要有requestId的会诊医嘱都可以处理
@@ -3656,7 +3771,6 @@ function handleSingOut() {
adviceType: item.adviceType, adviceType: item.adviceType,
statusEnum: item.statusEnum, statusEnum: item.statusEnum,
requestId: item.requestId, requestId: item.requestId,
adviceName: item.adviceName
}))); })));
// 🔧 BugFix: 将requestId转换为数字类型 // 🔧 BugFix: 将requestId转换为数字类型
@@ -3896,9 +4010,8 @@ function convertValues(row, index) {
row.dose = row.doseQuantity / row.partPercent; row.dose = row.doseQuantity / row.partPercent;
break; break;
} }
// 🔧 Bug #273 修复:单次剂量变化后重新计算总量
calculateTotalAmount(row, index);
}); });
// calculateTotalAmount(row, index);
} }
// 单次剂量数量改变时自动计算总量 // 单次剂量数量改变时自动计算总量
@@ -3924,106 +4037,35 @@ function convertDoseValues(row, index) {
row.doseQuantity = row.dose * row.partPercent; row.doseQuantity = row.dose * row.partPercent;
break; break;
} }
// 🔧 Bug #273 修复:单次剂量变化后重新计算总量
calculateTotalAmount(row, index);
}); });
// calculateTotalAmount(row, index);
} }
// 总量计算,仅适用只有两种单位的情况 // 总量计算,仅适用只有两种单位的情况
function calculateTotalAmount(row, index) { function calculateTotalAmount(row, index) {
nextTick(() => { nextTick(() => {
// 🔧 Bug #273 调试日志 // 🔧 Bug Fix: 处理耗材类型的总金额计算
console.log('[calculateTotalAmount] 开始计算', { if (row.adviceType == 4) {
adviceType: row.adviceType, row.totalPrice = (row.quantity * row.unitPrice).toFixed(6);
rateCode: row.rateCode, // 🔧 Bug Fix: 确保耗材的minUnitQuantity和minUnitCode正确设置
dispensePerDuration: row.dispensePerDuration, row.minUnitQuantity = row.quantity;
doseQuantity: row.doseQuantity, row.minUnitCode = row.unitCode;
partAttributeEnum: row.partAttributeEnum, row.minUnitCode_dictText = row.unitCode_dictText;
unitCode: row.unitCode,
minUnitCode: row.minUnitCode
});
// 项目为西药或中成药时
if (row.adviceType != 1 && row.adviceType != 2) {
console.log('[calculateTotalAmount] 非西药/中成药,跳过计算');
return; return;
} }
// 🔧 Bug #273 修复:使用字典数据计算频次对应的每日次数 if (row.adviceType == 2) {
function getRateCount(rateCode) { calculateTotalPrice(row, index);
// 先从字典中查找
const rateDict = rate_code.value?.find(item => item.value === rateCode);
if (rateDict && rateDict.remark) {
return Number(rateDict.remark);
}
// 回退到固定映射
const frequencyMap = {
ST: 1, QD: 1, BID: 2, TID: 3, QID: 4, QN: 1,
QOD: 0.5, QW: 1/7, BIW: 2/7, TIW: 3/7, QOW: 1/14
};
return frequencyMap[rateCode] || 1;
}
// 情况1: 根据用药天数和频次计算
if (row.rateCode && row.dispensePerDuration) {
const rateCount = getRateCount(row.rateCode);
const count = rateCount * row.dispensePerDuration;
console.log('[calculateTotalAmount] 计算count:', { rateCount, days: row.dispensePerDuration, count });
if (!count) return;
let quantity;
if (row.unitCode == row.minUnitCode) {
// 🔧 使用最小单位时,计算的是最小单位数量(如袋数)
quantity = calculateQuantityBySplitType(row.partAttributeEnum, row.doseQuantity, count, row.partPercent);
row.quantity = quantity;
row.totalPrice = (quantity * row.minUnitPrice).toFixed(2);
console.log('[calculateTotalAmount] 计算结果(最小单位):', { quantity, totalPrice: row.totalPrice, partPercent: row.partPercent });
} else {
// 🔧 使用包装单位时,计算的是包装单位数量(如盒数)
quantity = calculateQuantity(
row.partAttributeEnum,
row.doseQuantity,
count,
row.partPercent
);
row.quantity = quantity;
row.totalPrice = (quantity * row.unitPrice).toFixed(2);
console.log('[calculateTotalAmount] 计算结果(包装单位):', { quantity, totalPrice: row.totalPrice, partPercent: row.partPercent });
}
return; return;
} }
console.log('[calculateTotalAmount] 条件不满足,未计算(rateCode或dispensePerDuration为空)');
});
}
// 总量计算,仅适用只有两种单位的情况
function calculateTotalAmount2(row, index) {
nextTick(() => {
// 项目为西药或中成药时
if (row.adviceType != 1 && row.adviceType != 2) { if (row.adviceType != 1 && row.adviceType != 2) {
return; return;
} }
// 🔧 Bug #273 修复:使用字典数据计算频次对应的每日次数
function getRateCount(rateCode) {
// 先从字典中查找
const rateDict = rate_code.value?.find(item => item.value === rateCode);
if (rateDict && rateDict.remark) {
return Number(rateDict.remark);
}
// 回退到固定映射
const frequencyMap = {
ST: 1, QD: 1, BID: 2, TID: 3, QID: 4, QN: 1,
QOD: 0.5, QW: 1/7, BIW: 2/7, TIW: 3/7, QOW: 1/14
};
return frequencyMap[rateCode] || 1;
}
// 情况1: 根据用药天数和频次计算 // 情况1: 根据用药天数和频次计算
if (row.rateCode && row.dispensePerDuration) { if (row.rateCode && row.dispensePerDuration) {
const rateCount = getRateCount(row.rateCode); const count = calculateQuantityByDays(row.rateCode, row.dispensePerDuration);
const count = rateCount * row.dispensePerDuration;
if (!count) return; if (!count) return;
let quantity; let quantity;
@@ -4043,62 +4085,100 @@ function calculateTotalAmount2(row, index) {
} }
return; return;
} }
// 情况2: 中成药兼容旧逻辑
if (row.adviceType == 2) {
if (row.partPercent == 1) {
row.totalPrice = (row.quantity * row.unitPrice).toFixed(6);
} else {
// 拆零比不为1时 如果当前总量单位是大单位,总价等于数量乘以大单位价格 否则总价等于数量乘以小单位价格
if (row.unitCodeList.find((k) => k.value == row.unitCode).type == 'unit') {
row.totalPrice = (row.quantity * row.unitPrice).toFixed(6);
} else {
row.totalPrice = (row.quantity * row.minUnitPrice).toFixed(6);
}
}
} else if (row.adviceType == 1) {
if (row.rateCode && row.dispensePerDuration) {
// 根据用药天数和用药频次计算数量,医生按顺序填的情况
let count = calculateQuantityByDays(row.rateCode, row.dispensePerDuration);
if (count) {
let quantity;
if (row.unitCode == row.minUnitCode) {
quantity = calculateQuantityBySplitType(row.partAttributeEnum, row.doseQuantity, count);
prescriptionList.value[index].quantity = quantity;
prescriptionList.value[index].totalPrice = (quantity * row.minUnitPrice).toFixed(6);
} else {
quantity = calculateQuantity(
row.partAttributeEnum,
row.doseQuantity,
count,
row.partPercent
);
prescriptionList.value[index].quantity = quantity;
prescriptionList.value[index].totalPrice = (quantity * row.unitPrice).toFixed(6);
}
}
} else if (row.quantity) {
// 如果医生开药先填总量 直接计算总价格
if (row.unitCode == row.minUnitCode) {
prescriptionList.value[index].totalPrice = (row.quantity * row.minUnitPrice).toFixed(6);
} else {
prescriptionList.value[index].totalPrice = (row.quantity * row.unitPrice).toFixed(6);
}
}
return;
}
// 情况3: 医生先填总量
if (row.quantity) {
if (row.unitCode == row.minUnitCode) {
prescriptionList.value[index].totalPrice = (row.quantity * row.minUnitPrice).toFixed(6);
} else {
prescriptionList.value[index].totalPrice = (row.quantity * row.unitPrice).toFixed(6);
}
}
}); });
} }
/** /**
* 根据门诊拆分类型计算总药量 - 最小单位 * 根据门诊拆分类型计算总药量
* 🔧 Bug #273 修复:统一使用拆零比计算 *
* @param type 门诊拆分类型 * @param type 门诊拆分类型
* @param dose 单次剂量 最小单位 * @param dose 单次剂量 最小单位
* @param count 用药频次和用药天数计算出的总数 * @param count 用药频次和用药天数计算出的总数
* @param partPercent 拆零比
*/ */
function calculateQuantityBySplitType(type, dose, count, partPercent) { function calculateQuantityBySplitType(type, dose, count) {
// 先计算最小单位总量
const minUnitTotal = dose * count;
switch (type) { switch (type) {
case 1: // 门诊按最小单位每次量向上取整 case 1: // 门诊按最小单位每次量向上取整
// 每次用量向上取整,然后计算总量 return Math.ceil(dose) * count;
return Math.ceil(Math.ceil(dose) * count / partPercent);
case 2: // 门诊按包装单位不可拆分 case 2: // 门诊按包装单位不可拆分
// 总量向上取整到包装单位 return Math.ceil(dose * count);
return Math.ceil(minUnitTotal / partPercent);
case 3: // 门诊按最小单位总量向上取整 case 3: // 门诊按最小单位总量向上取整
// 总量向上取整,然后转换为包装单位 return Math.ceil(dose * count);
return Math.ceil(minUnitTotal / partPercent);
case 4: // 门诊按包装单位每次量向上取整 case 4: // 门诊按包装单位每次量向上取整
// 每次用量转换为包装单位后向上取整 return Math.ceil(dose) * count;
return Math.ceil(Math.ceil(dose / partPercent) * count);
} }
} }
/** /**
* 根据门诊拆分类型计算总药量 - 包装单位 * 根据门诊拆分类型计算总药量
* 🔧 Bug #273 修复:统一使用拆零比计算 *
* @param type 门诊拆分类型 * @param type 门诊拆分类型
* @param dose 单次剂量 最小单位 * @param dose 单次剂量 最小单位
* @param count 用药频次和用药天数计算出的总数 * @param count 用药频次和用药天数计算出的总数
* @param partPercent 拆零比
*/ */
function calculateQuantity(type, dose, count, partPercent) { function calculateQuantity(type, dose, count, partPercent) {
// 先计算最小单位总量
const minUnitTotal = dose * count;
switch (type) { switch (type) {
case 1: // 门诊按最小单位每次量向上取整 case 1: // 门诊按最小单位每次量向上取整
// 每次用量向上取整,然后计算总量 return Math.ceil(dose / partPercent) * count;
return Math.ceil(Math.ceil(dose) * count / partPercent);
case 2: // 门诊按包装单位不可拆分 case 2: // 门诊按包装单位不可拆分
// 总量向上取整到包装单位 return Math.ceil(dose * count);
return Math.ceil(minUnitTotal / partPercent);
case 3: // 门诊按最小单位总量向上取整 case 3: // 门诊按最小单位总量向上取整
// 总量向上取整,然后转换为包装单位 return Math.ceil((dose / partPercent) * count);
return Math.ceil(minUnitTotal / partPercent);
case 4: // 门诊按包装单位每次量向上取整 case 4: // 门诊按包装单位每次量向上取整
// 每次用量转换为包装单位后向上取整 return Math.ceil(dose) * count;
return Math.ceil(Math.ceil(dose / partPercent) * count);
} }
} }
@@ -4482,16 +4562,6 @@ defineExpose({ getListInfo, getDiagnosisInfo });
margin-bottom: 0px; margin-bottom: 0px;
} }
/* 🔧 Bug #273 拆零比提示样式 */
.part-percent-hint {
color: #909399;
font-size: 12px;
white-space: nowrap;
margin-left: 8px;
display: inline-flex;
align-items: center;
}
/* V1.3 风格侧边栏 - 组套列表样式 */ /* V1.3 风格侧边栏 - 组套列表样式 */
.order-group-container { .order-group-container {
display: flex; display: flex;

Some files were not shown because too many files have changed in this diff Show More