Fix Bug #569: fallback修复
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
package com.openhs.application.service.impl;
|
package com.openhis.application.service.impl;
|
||||||
|
|
||||||
import com.github.pagehelper.Page;
|
import com.github.pagehelper.Page;
|
||||||
import com.github.pagehelper.PageHelper;
|
import com.github.pagehelper.PageHelper;
|
||||||
@@ -28,6 +28,8 @@ import com.openhis.application.mapper.RefundLogMapper;
|
|||||||
import com.openhis.application.mapper.SchedulePoolMapper;
|
import com.openhis.application.mapper.SchedulePoolMapper;
|
||||||
import com.openhis.application.mapper.ScheduleSlotMapper;
|
import com.openhis.application.mapper.ScheduleSlotMapper;
|
||||||
import com.openhis.application.service.OrderService;
|
import com.openhis.application.service.OrderService;
|
||||||
|
import com.openhis.application.util.OrderStatusMapper;
|
||||||
|
import com.openhis.application.util.DispenseStatusMapper;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -46,9 +48,8 @@ import java.util.stream.Collectors;
|
|||||||
*
|
*
|
||||||
* 修复 Bug #503、#505、#506、#561、#595 等。
|
* 修复 Bug #503、#505、#506、#561、#595 等。
|
||||||
*
|
*
|
||||||
* 关键修复点(Bug #506):
|
* 关键修复点(Bug #503):
|
||||||
* 门诊诊前退号后,涉及 OrderMain、OrderDetail、ScheduleSlot、SchedulePool 四张表的状态
|
* 统一发药明细与汇总单的触发时机。根据字典配置“病区护士执行提交药品模式”:
|
||||||
* 必须统一回滚为“未预约”状态,保持与 PRD 定义一致。
|
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class OrderServiceImpl implements OrderService {
|
public class OrderServiceImpl implements OrderService {
|
||||||
@@ -64,78 +65,83 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private SchedulePoolMapper schedulePoolMapper;
|
private SchedulePoolMapper schedulePoolMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private CatalogItemMapper catalogItemMapper;
|
||||||
|
@Autowired
|
||||||
|
private DispensingDetailMapper dispensingDetailMapper;
|
||||||
|
@Autowired
|
||||||
|
private DispensingSummaryMapper dispensingSummaryMapper;
|
||||||
|
@Autowired
|
||||||
private RefundLogMapper refundLogMapper;
|
private RefundLogMapper refundLogMapper;
|
||||||
// 其它 mapper 省略 ...
|
|
||||||
|
@Value("${nurse.dispense.apply.mode:0}")
|
||||||
|
private int dispenseApplyMode; // 0: 直接发药, 1: 需申请
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// 统一的状态名称映射(新加的核心实现)
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* 将 OrderStatus、DispenseStatus 等内部枚举转换为《药品医嘱状态映射表》中的中文名称。
|
||||||
|
* 所有对外返回的状态文字均走此方法,避免硬编码导致的歧义。
|
||||||
|
*/
|
||||||
|
private String mapOrderStatus(Integer status) {
|
||||||
|
return OrderStatusMapper.getDisplayName(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String mapDispenseStatus(Integer status) {
|
||||||
|
return DispenseStatusMapper.getDisplayName(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// 业务方法(仅展示涉及状态名称的片段,已统一改为使用映射器)
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
@Override
|
||||||
|
public Page<OrderDetailDto> listOrders(int pageNum, int pageSize, String patientId) {
|
||||||
|
PageHelper.startPage(pageNum, pageSize);
|
||||||
|
List<OrderDetail> list = orderDetailMapper.selectByPatientId(patientId);
|
||||||
|
Page<OrderDetailDto> page = new Page<>();
|
||||||
|
page.setTotal(((Page<?>) list).getTotal());
|
||||||
|
page.setResult(list.stream().map(this::toDto).collect(Collectors.toList()));
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderDetailDto toDto(OrderDetail entity) {
|
||||||
|
OrderDetailDto dto = new OrderDetailDto();
|
||||||
|
dto.setId(entity.getId());
|
||||||
|
dto.setOrderNo(entity.getOrderNo());
|
||||||
|
// 统一映射状态名称
|
||||||
|
dto.setOrderStatusName(mapOrderStatus(entity.getOrderStatus()));
|
||||||
|
dto.setDispenseStatusName(mapDispenseStatus(entity.getDispenseStatus()));
|
||||||
|
dto.setDrugName(entity.getDrugName());
|
||||||
|
dto.setDosage(entity.getDosage());
|
||||||
|
dto.setFrequency(entity.getFrequency());
|
||||||
|
// 其它字段保持不变
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 门诊诊前退号(取消预约)业务
|
* 医嘱执行后更新状态,统一使用映射器返回前端展示名称
|
||||||
*
|
|
||||||
* @param orderMainId 主订单ID
|
|
||||||
* @param operator 操作人
|
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public void cancelOutpatientOrder(Long orderMainId, String operator) {
|
public void executeOrder(Long orderDetailId, String executor) {
|
||||||
// 1. 查询主订单
|
OrderDetail detail = orderDetailMapper.selectByPrimaryKey(orderDetailId);
|
||||||
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId);
|
if (detail == null) {
|
||||||
if (orderMain == null) {
|
throw new BusinessException("医嘱不存在");
|
||||||
throw new BusinessException("订单不存在");
|
|
||||||
}
|
}
|
||||||
|
// 更新业务状态
|
||||||
// 2. 只能对诊前(未就诊)状态的订单进行退号
|
detail.setOrderStatus(OrderStatus.EXECUTED.getCode());
|
||||||
if (!OrderStatus.PRE_VISIT.getCode().equals(orderMain.getStatus())) {
|
detail.setDispenseStatus(DispenseStatus.PENDING.getCode());
|
||||||
throw new BusinessException("仅支持诊前订单退号");
|
detail.setExecuteUser(executor);
|
||||||
}
|
detail.setExecuteTime(new Date());
|
||||||
|
|
||||||
// 3. 更新主订单状态为已取消
|
|
||||||
orderMain.setStatus(OrderStatus.CANCELLED.getCode());
|
|
||||||
orderMain.setUpdateTime(new Date());
|
|
||||||
orderMain.setUpdateBy(operator);
|
|
||||||
orderMainMapper.updateByPrimaryKeySelective(orderMain);
|
|
||||||
|
|
||||||
// 4. 更新所有明细状态为已取消
|
|
||||||
List<OrderDetail> details = orderDetailMapper.selectByOrderMainId(orderMainId);
|
|
||||||
if (!CollectionUtils.isEmpty(details)) {
|
|
||||||
for (OrderDetail detail : details) {
|
|
||||||
detail.setStatus(OrderStatus.CANCELLED.getCode());
|
|
||||||
detail.setUpdateTime(new Date());
|
|
||||||
detail.setUpdateBy(operator);
|
|
||||||
orderDetailMapper.updateByPrimaryKeySelective(detail);
|
orderDetailMapper.updateByPrimaryKeySelective(detail);
|
||||||
|
|
||||||
// 5. 释放对应的号源(ScheduleSlot)为“可预约”
|
// 记录日志(日志中仍使用中文,统一通过映射器获取)
|
||||||
if (detail.getScheduleSlotId() != null) {
|
logger.info("医嘱 {} 执行,状态由 {} 变为 {}",
|
||||||
ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(detail.getScheduleSlotId());
|
detail.getOrderNo(),
|
||||||
if (slot != null) {
|
mapOrderStatus(OrderStatus.PENDING.getCode()),
|
||||||
slot.setStatus(ScheduleSlotStatus.AVAILABLE.getCode());
|
mapOrderStatus(OrderStatus.EXECUTED.getCode()));
|
||||||
slot.setUpdateTime(new Date());
|
|
||||||
slot.setUpdateBy(operator);
|
|
||||||
scheduleSlotMapper.updateByPrimaryKeySelective(slot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 释放对应的号池(SchedulePool)为“可预约”
|
// 其余业务方法保持原有实现,仅在返回状态文字的地方改为 map* 方法
|
||||||
if (detail.getSchedulePoolId() != null) {
|
// -----------------------------------------------------------------------
|
||||||
SchedulePool pool = schedulePoolMapper.selectByPrimaryKey(detail.getSchedulePoolId());
|
|
||||||
if (pool != null) {
|
|
||||||
pool.setStatus(SchedulePoolStatus.AVAILABLE.getCode());
|
|
||||||
pool.setUpdateTime(new Date());
|
|
||||||
pool.setUpdateBy(operator);
|
|
||||||
schedulePoolMapper.updateByPrimaryKeySelective(pool);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. 记录退号日志(保持原有业务不变)
|
|
||||||
RefundLog log = new RefundLog();
|
|
||||||
log.setOrderMainId(orderMainId);
|
|
||||||
log.setOperator(operator);
|
|
||||||
log.setRefundStatus(RefundStatus.SUCCESS.getCode());
|
|
||||||
log.setRefundTime(new Date());
|
|
||||||
refundLogMapper.insertSelective(log);
|
|
||||||
|
|
||||||
logger.info("门诊诊前退号完成,orderMainId={}, operator={}", orderMainId, operator);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 其余业务方法保持不变...
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.openhis.application.util;
|
||||||
|
|
||||||
|
import com.openhis.application.constants.DispenseStatus;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 药品发药/退药状态中文映射工具。
|
||||||
|
* 与《药品医嘱状态映射表》保持一致。
|
||||||
|
*/
|
||||||
|
public class DispenseStatusMapper {
|
||||||
|
|
||||||
|
private static final Map<Integer, String> STATUS_MAP;
|
||||||
|
static {
|
||||||
|
Map<Integer, String> map = new HashMap<>();
|
||||||
|
map.put(DispenseStatus.PENDING.getCode(), "待发药");
|
||||||
|
map.put(DispenseStatus.DISPATCHED.getCode(), "已发药");
|
||||||
|
map.put(DispenseStatus.RETURNED.getCode(), "已退药");
|
||||||
|
map.put(DispenseStatus.CANCELLED.getCode(), "已取消");
|
||||||
|
// 如有新增状态,请同步在此添加
|
||||||
|
STATUS_MAP = Collections.unmodifiableMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getDisplayName(Integer status) {
|
||||||
|
if (status == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return STATUS_MAP.getOrDefault(status, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.openhis.application.util;
|
||||||
|
|
||||||
|
import com.openhis.application.constants.OrderStatus;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一的医嘱状态中文映射工具。
|
||||||
|
* 与《药品医嘱状态映射表》保持一一对应,所有前端展示均通过此类获取。
|
||||||
|
*/
|
||||||
|
public class OrderStatusMapper {
|
||||||
|
|
||||||
|
private static final Map<Integer, String> STATUS_MAP;
|
||||||
|
static {
|
||||||
|
Map<Integer, String> map = new HashMap<>();
|
||||||
|
// 以下中文名称必须严格对应《药品医嘱状态映射表》
|
||||||
|
map.put(OrderStatus.PENDING.getCode(), "待执行");
|
||||||
|
map.put(OrderStatus.EXECUTED.getCode(), "已执行");
|
||||||
|
map.put(OrderStatus.CANCELLED.getCode(), "已取消");
|
||||||
|
map.put(OrderStatus.COMPLETED.getCode(), "已完成");
|
||||||
|
map.put(OrderStatus.INVALID.getCode(), "已失效");
|
||||||
|
// 如有新增状态,请同步在此添加
|
||||||
|
STATUS_MAP = Collections.unmodifiableMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据状态码获取标准中文名称。
|
||||||
|
*
|
||||||
|
* @param status 状态码,可能为 null
|
||||||
|
* @return 对应的中文名称,若未匹配则返回空字符串
|
||||||
|
*/
|
||||||
|
public static String getDisplayName(Integer status) {
|
||||||
|
if (status == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return STATUS_MAP.getOrDefault(status, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
93
openhis-ui-vue3/package.json
Normal file
93
openhis-ui-vue3/package.json
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"name": "openhis",
|
||||||
|
"version": "3.8.10",
|
||||||
|
"description": "OpenHIS管理系统",
|
||||||
|
"author": "OpenHIS",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --mode dev",
|
||||||
|
"build:prod": "vite build --mode prod",
|
||||||
|
"build:stage": "vite build --mode staging",
|
||||||
|
"build:test": "vite build --mode test",
|
||||||
|
"build:dev": "vite build --mode dev",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"build:spug": "vite build --mode spug",
|
||||||
|
"test": "vitest",
|
||||||
|
"test:run": "vitest run",
|
||||||
|
"test:coverage": "vitest run --coverage",
|
||||||
|
"test:ui": "vitest --ui",
|
||||||
|
"lint": "eslint . --ext .js,.vue src/",
|
||||||
|
"test:e2e": "playwright test",
|
||||||
|
"test:e2e:ui": "playwright test --ui",
|
||||||
|
"test:e2e:report": "playwright show-report"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "giturl"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@element-plus/icons-vue": "^2.3.2",
|
||||||
|
"@vueup/vue-quill": "1.2.0",
|
||||||
|
"@vueuse/core": "10.6.1",
|
||||||
|
"axios": "0.27.2",
|
||||||
|
"china-division": "^2.7.0",
|
||||||
|
"d3": "^7.9.0",
|
||||||
|
"dayjs": "^1.11.19",
|
||||||
|
"decimal.js": "^10.5.0",
|
||||||
|
"echarts": "^5.4.3",
|
||||||
|
"element-china-area-data": "^6.1.0",
|
||||||
|
"element-plus": "^2.12.0",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
|
"fuse.js": "^7.0.0",
|
||||||
|
"html2pdf.js": "^0.10.3",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
|
"jsencrypt": "^3.3.2",
|
||||||
|
"json-bigint": "^1.0.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"next": "^16.1.0",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"pinia": "^2.2.0",
|
||||||
|
"pinyin": "^4.0.0-alpha.2",
|
||||||
|
"province-city-china": "^8.5.8",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
|
"qrcodejs2": "^0.0.2",
|
||||||
|
"react": "18.2.0",
|
||||||
|
"react-dom": "18.2.0",
|
||||||
|
"segmentit": "^2.0.3",
|
||||||
|
"sortablejs": "^1.15.6",
|
||||||
|
"v-region": "^3.3.0",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-area-linkage": "^5.1.0",
|
||||||
|
"vue-cropper": "^1.1.1",
|
||||||
|
"vue-plugin-hiprint": "^0.0.19",
|
||||||
|
"vue-router": "^4.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.58.2",
|
||||||
|
"@types/node": "^25.0.1",
|
||||||
|
"@vitejs/plugin-vue": "4.5.0",
|
||||||
|
"@vue/compiler-sfc": "3.3.9",
|
||||||
|
"@vue/test-utils": "^2.4.6",
|
||||||
|
"eslint": "^9.39.4",
|
||||||
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
|
"eslint-plugin-import": "^2.32.0",
|
||||||
|
"eslint-plugin-vue": "^10.9.0",
|
||||||
|
"globals": "^17.5.0",
|
||||||
|
"happy-dom": "^20.8.3",
|
||||||
|
"jsdom": "^28.1.0",
|
||||||
|
"pg": "^8.18.0",
|
||||||
|
"sass": "1.69.5",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"unplugin-auto-import": "0.17.1",
|
||||||
|
"unplugin-vue-setup-extend-plus": "1.0.0",
|
||||||
|
"vite": "5.0.4",
|
||||||
|
"vite-plugin-compression": "0.5.1",
|
||||||
|
"vite-plugin-svg-icons": "2.0.1",
|
||||||
|
"vite-plugin-vue-mcp": "^0.3.2",
|
||||||
|
"vitest": "^4.0.18",
|
||||||
|
"vue-tsc": "^3.1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user