Compare commits
14 Commits
b747f80507
...
bug/334
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c5353cf8b | ||
|
|
8a84b40ee5 | ||
| f6b39a4815 | |||
|
|
1b3d4e3dc0 | ||
|
|
cb46461ede | ||
| 3b0a359412 | |||
| 6fa26e895d | |||
| 8ab8691c17 | |||
|
|
35b8a7d10a | ||
| 22de02f132 | |||
| 11244aa48f | |||
| 0a5f26e9c0 | |||
| 4a8e9b5a22 | |||
| bfb2491842 |
@@ -1,11 +1,15 @@
|
||||
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.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
@@ -24,6 +28,14 @@ public class ApplicationConfig {
|
||||
*/
|
||||
@Bean
|
||||
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")));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.springframework.stereotype.Service;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -123,6 +124,7 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
||||
if (query == null) {
|
||||
query = new com.openhis.appointmentmanage.dto.TicketQueryDTO();
|
||||
}
|
||||
normalizeQueryStatus(query);
|
||||
|
||||
// 2. 构造 MyBatis 的分页对象 (传入前端给的当前页和每页条数)
|
||||
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.setSeqNo(raw.getSeqNo());
|
||||
dto.setBusNo(String.valueOf(raw.getSlotId()));
|
||||
dto.setDoctor(raw.getDoctor());
|
||||
dto.setDepartment(raw.getDepartmentName()); // 注意:以前这里传成了ID,导致前端出Bug,现在修复成了真正的科室名
|
||||
dto.setFee(raw.getFee());
|
||||
dto.setPatientName(raw.getPatientName());
|
||||
dto.setPatientId(raw.getPatientId() != null ? String.valueOf(raw.getPatientId()) : null);
|
||||
dto.setPatientId(raw.getMedicalCard());
|
||||
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) {
|
||||
dto.setTicketType("expert");
|
||||
} else {
|
||||
dto.setTicketType("general");
|
||||
}
|
||||
|
||||
// 拼接就诊时间
|
||||
if (raw.getScheduleDate() != null && raw.getExpectTime() != null) {
|
||||
dto.setDateTime(raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString());
|
||||
try {
|
||||
dto.setAppointmentDate(
|
||||
new java.text.SimpleDateFormat("yyyy-MM-dd").parse(raw.getScheduleDate().toString()));
|
||||
String timeStr = raw.getAppointmentTime() != null ? raw.getAppointmentTime() : (raw.getScheduleDate().toString() + " " + raw.getExpectTime().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) {
|
||||
dto.setAppointmentDate(new java.util.Date());
|
||||
}
|
||||
}
|
||||
|
||||
// 精准状态翻译!把底层的1和2,翻译回前端能懂的中文
|
||||
if (Boolean.TRUE.equals(raw.getIsStopped())) {
|
||||
dto.setStatus("已停诊");
|
||||
} else {
|
||||
Integer slotStatus = raw.getSlotStatus();
|
||||
if (slotStatus != null) {
|
||||
if (SlotStatus.BOOKED.equals(slotStatus)) {
|
||||
dto.setStatus(AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus()) ? "已取号" : "已预约");
|
||||
} else if (SlotStatus.STOPPED.equals(slotStatus)) {
|
||||
if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
|
||||
dto.setStatus("已取号");
|
||||
} 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("已停诊");
|
||||
} else if (SlotStatus.LOCKED.equals(slotStatus)) {
|
||||
dto.setStatus("已锁定");
|
||||
} else {
|
||||
dto.setStatus("未预约");
|
||||
}
|
||||
@@ -198,6 +225,62 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
||||
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
|
||||
public R<?> listDoctorAvailability(com.openhis.appointmentmanage.dto.TicketQueryDTO query) {
|
||||
if (query == null) {
|
||||
@@ -237,12 +320,13 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
||||
// --- 基础字段处理 ---
|
||||
// 注意:这里已经变成了极其舒服的 .getSlotId() 方法调用,告别魔鬼字符串!
|
||||
dto.setSlot_id(raw.getSlotId());
|
||||
dto.setSeqNo(raw.getSeqNo());
|
||||
dto.setBusNo(String.valueOf(raw.getSlotId())); // 暂时借用真实槽位ID做唯一流水号
|
||||
dto.setDoctor(raw.getDoctor());
|
||||
dto.setDepartment(raw.getDepartmentName());
|
||||
dto.setFee(raw.getFee());
|
||||
dto.setPatientName(raw.getPatientName());
|
||||
dto.setPatientId(raw.getPatientId() != null ? String.valueOf(raw.getPatientId()) : null);
|
||||
dto.setPatientId(raw.getMedicalCard());
|
||||
dto.setPhone(raw.getPhone());
|
||||
|
||||
// --- 号源类型处理 (普通/专家) ---
|
||||
@@ -258,9 +342,13 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
||||
if (raw.getScheduleDate() != null && raw.getExpectTime() != null) {
|
||||
dto.setDateTime(raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString());
|
||||
try {
|
||||
dto.setAppointmentDate(
|
||||
new java.text.SimpleDateFormat("yyyy-MM-dd").parse(raw.getScheduleDate().toString()));
|
||||
String timeStr = raw.getAppointmentTime() != null ? raw.getAppointmentTime() : (raw.getScheduleDate().toString() + " " + raw.getExpectTime().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) {
|
||||
log.error("时间解析失败", e);
|
||||
dto.setAppointmentDate(new java.util.Date());
|
||||
}
|
||||
}
|
||||
@@ -273,10 +361,22 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
||||
// 第二关:看独立的细分槽位状态 (0: 可用, 1: 已预约, 2: 已取消...)
|
||||
Integer slotStatus = raw.getSlotStatus();
|
||||
if (slotStatus != null) {
|
||||
if (SlotStatus.BOOKED.equals(slotStatus)) {
|
||||
dto.setStatus(AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus()) ? "已取号" : "已预约");
|
||||
} else if (SlotStatus.STOPPED.equals(slotStatus)) {
|
||||
dto.setStatus("已停诊"); // 视业务可改回已取消
|
||||
if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
|
||||
dto.setStatus("已取号");
|
||||
} 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("已停诊");
|
||||
} else if (SlotStatus.LOCKED.equals(slotStatus)) {
|
||||
dto.setStatus("已锁定");
|
||||
} else {
|
||||
dto.setStatus("未预约");
|
||||
}
|
||||
@@ -355,14 +455,11 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
||||
if (patient != null) {
|
||||
Integer genderEnum = patient.getGenderEnum();
|
||||
if (genderEnum != null) {
|
||||
switch (genderEnum) {
|
||||
case 1:
|
||||
if (Integer.valueOf(1).equals(genderEnum)) {
|
||||
dto.setGender("男");
|
||||
break;
|
||||
case 2:
|
||||
} else if (Integer.valueOf(2).equals(genderEnum)) {
|
||||
dto.setGender("女");
|
||||
break;
|
||||
default:
|
||||
} else {
|
||||
dto.setGender("未知");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ public class TicketDto {
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long slot_id;
|
||||
|
||||
/**
|
||||
* 号源序号(对应 adm_schedule_slot.seq_no)
|
||||
*/
|
||||
private Integer seqNo;
|
||||
|
||||
/**
|
||||
* 号源编码
|
||||
*/
|
||||
@@ -99,4 +104,15 @@ public class TicketDto {
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long doctorId;
|
||||
|
||||
/**
|
||||
* 真实患者ID(数据库主键,区别于 patientId 存的就诊卡号)
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long realPatientId;
|
||||
|
||||
/**
|
||||
* 身份证号
|
||||
*/
|
||||
private String idCard;
|
||||
}
|
||||
|
||||
@@ -73,11 +73,10 @@ public class OutpatientPricingAppServiceImpl implements IOutpatientPricingAppSer
|
||||
} else {
|
||||
adviceTypes = List.of(1, 2, 3);
|
||||
}
|
||||
// 门诊划价:不要强制 pricingFlag=1 参与过滤(wor_activity_definition.pricing_flag 可能为 0),
|
||||
// 否则会导致诊疗项目(adviceType=3)查询结果为空 records=[]
|
||||
String categoryCode = adviceBaseDto != null ? adviceBaseDto.getCategoryCode() : null;
|
||||
// 门诊划价:仅返回划价标记为“是”的项目
|
||||
return iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId, null,
|
||||
organizationId, pageNo, pageSize, null, adviceTypes, null, categoryCode);
|
||||
organizationId, pageNo, pageSize, Whether.YES.getValue(), adviceTypes, null, categoryCode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ import com.openhis.common.enums.ybenums.YbPayment;
|
||||
import com.openhis.common.utils.EnumUtils;
|
||||
import com.openhis.common.utils.HisPageUtils;
|
||||
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.RefundLog;
|
||||
import com.openhis.financial.service.IRefundLogService;
|
||||
@@ -48,6 +52,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -97,6 +102,15 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
||||
@Resource
|
||||
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);
|
||||
|
||||
@@ -399,6 +418,74 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 补打挂号
|
||||
* 补打挂号不需要修改数据库,只需要返回成功即可,前端已有所有需要的数据用于打印
|
||||
|
||||
@@ -371,7 +371,7 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
|
||||
index, // 序号从1开始
|
||||
schedule.getOrgName() != null ? schedule.getOrgName() : "",
|
||||
schedule.getPatientName() != null ? schedule.getPatientName() : "",
|
||||
schedule.getVisitId() != null ? schedule.getVisitId().toString() : "",
|
||||
schedule.getIdentifierNo() != null ? schedule.getIdentifierNo() : "",
|
||||
schedule.getOperCode() != null ? schedule.getOperCode() : "",
|
||||
schedule.getOperName() != null ? schedule.getOperName() : "",
|
||||
schedule.getApplyDeptName() != null ? schedule.getApplyDeptName() : "",
|
||||
|
||||
@@ -24,6 +24,11 @@ public class OpCreateScheduleDto {
|
||||
*/
|
||||
private Long visitId;
|
||||
|
||||
/**
|
||||
* 就诊卡号
|
||||
*/
|
||||
private String identifierNo;
|
||||
|
||||
/**
|
||||
* 手术编码
|
||||
*/
|
||||
|
||||
@@ -1201,6 +1201,47 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
chargeItem.setServiceId(deviceRequest.getId()); // 医疗服务ID
|
||||
chargeItem.setProductTable(adviceSaveDto.getAdviceTableName());// 产品所在表
|
||||
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,如果没有则自动创建
|
||||
Long accountId = adviceSaveDto.getAccountId();
|
||||
if (accountId == null) {
|
||||
|
||||
@@ -273,12 +273,28 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
|
||||
*/
|
||||
@Override
|
||||
public R<?> saveDoctorDiagnosisNew(SaveDiagnosisParam saveDiagnosisParam) {
|
||||
// 参数校验:确保诊断列表不为空
|
||||
if (saveDiagnosisParam == null) {
|
||||
return R.fail(MessageUtils.message(PromptMsgConstant.Common.M00009, new Object[] { "保存诊断参数" }));
|
||||
}
|
||||
|
||||
// 患者id
|
||||
Long patientId = saveDiagnosisParam.getPatientId();
|
||||
// 就诊ID
|
||||
Long encounterId = saveDiagnosisParam.getEncounterId();
|
||||
// 诊断定义集合
|
||||
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);
|
||||
|
||||
|
||||
@@ -115,6 +115,15 @@ public class AdviceUtils {
|
||||
matched = true;
|
||||
// 检查库存是否充足
|
||||
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(); // 中药付数
|
||||
// 中草药医嘱的情况
|
||||
if (chineseHerbsDoseQuantity != null && chineseHerbsDoseQuantity.compareTo(BigDecimal.ZERO) > 0) {
|
||||
|
||||
@@ -261,7 +261,9 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
||||
|
||||
// 账户id,对应的账单列表
|
||||
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();
|
||||
|
||||
@@ -2331,7 +2333,9 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
||||
= iChargeItemService.getChargeItemBaseInfoByIds(prePaymentDto.getChargeItemIds());
|
||||
|
||||
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<>();
|
||||
Yb2303OutputSetInfo yb2303OutputSetInfo;
|
||||
@@ -2459,13 +2463,17 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
||||
List<ChargeItemBaseInfoDto> chargeItemBaseInfoByIds
|
||||
= iChargeItemService.getChargeItemBaseInfoByIds(paymentDto.getChargeItemIds());
|
||||
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());
|
||||
if (accountList.isEmpty()) {
|
||||
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;
|
||||
List<com.openhis.financial.model.PaymentResult> paymentResultList = new ArrayList<>();
|
||||
@@ -2475,7 +2483,9 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
||||
|
||||
// <3>收费详情按照收费批次进行分组后结算
|
||||
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()) {
|
||||
// paymentResult = new PaymentResult();
|
||||
|
||||
@@ -93,4 +93,9 @@ public class RequestFormPageDto {
|
||||
* 手术等级
|
||||
*/
|
||||
private Integer surgeryLevel;
|
||||
|
||||
/**
|
||||
* 就诊卡号
|
||||
*/
|
||||
private String identifierNo;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
cs.org_id,
|
||||
o.name AS org_name,
|
||||
cs.main_surgeon_name AS surgeon_name,
|
||||
pi.identifier_no AS patient_card_no
|
||||
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
|
||||
FROM op_schedule os
|
||||
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'
|
||||
@@ -89,7 +89,7 @@
|
||||
cs.apply_doctor_name AS apply_doctor_name,
|
||||
drf.create_time AS apply_time,
|
||||
os.surgery_nature AS surgeryType,
|
||||
pi.identifier_no AS patient_card_no
|
||||
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
|
||||
FROM op_schedule os
|
||||
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'
|
||||
@@ -150,7 +150,7 @@
|
||||
cs.org_id,
|
||||
o.name AS org_name,
|
||||
cs.main_surgeon_name AS surgeon_name,
|
||||
pi.identifier_no AS patient_card_no
|
||||
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
|
||||
FROM op_schedule os
|
||||
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'
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
<result column="anesthesia_type_enum" property="anesthesiaTypeEnum"/>
|
||||
<result column="incision_level" property="incisionLevel"/>
|
||||
<result column="surgery_level" property="surgeryLevel"/>
|
||||
<result column="identifier_no" property="identifierNo"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 分页查询申请单 -->
|
||||
@@ -92,7 +93,8 @@
|
||||
cs.anesthesia_type_enum,
|
||||
cs.incision_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
|
||||
LEFT JOIN cli_surgery cs ON cs.surgery_no = drf.prescription_no
|
||||
LEFT JOIN adm_patient ap ON ap.id = cs.patient_id
|
||||
@@ -100,6 +102,16 @@
|
||||
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 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>
|
||||
<if test="requestFormDto.surgeryNo != null and requestFormDto.surgeryNo != ''">
|
||||
AND drf.prescription_no LIKE CONCAT('%', #{requestFormDto.surgeryNo}, '%')
|
||||
|
||||
@@ -769,15 +769,21 @@ public class CommonConstants {
|
||||
}
|
||||
|
||||
/**
|
||||
* 号源槽位状态 (adm_schedule_slot.slot_status)
|
||||
* 号源槽位状态 (adm_schedule_slot.status)
|
||||
*/
|
||||
public interface SlotStatus {
|
||||
/** 可用 / 待预约 */
|
||||
Integer AVAILABLE = 0;
|
||||
/** 已预约 */
|
||||
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 CANCELLED = 3;
|
||||
/** 已退号 */
|
||||
Integer RETURNED = 4;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 号源池明细Entity
|
||||
*
|
||||
@@ -29,7 +31,7 @@ public class ScheduleSlot extends HisBaseEntity {
|
||||
/** 序号 */
|
||||
private Integer seqNo;
|
||||
|
||||
/** 序号状态: 0-可用,1-已预约,2-已取消,3-已过期等 */
|
||||
/** 序号状态: 0-可用,1-已预约,2-已取消/已停诊,3-已锁定,4-已签到,5-已退号 */
|
||||
private Integer status;
|
||||
|
||||
/** 预约订单ID */
|
||||
@@ -37,4 +39,7 @@ public class ScheduleSlot extends HisBaseEntity {
|
||||
|
||||
/** 预计叫号时间 */
|
||||
private LocalTime expectTime;
|
||||
|
||||
/** 签到时间 */
|
||||
private Date checkInTime;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.time.LocalTime;
|
||||
public class TicketSlotDTO {
|
||||
// 基础信息
|
||||
private Long slotId;
|
||||
private Integer seqNo;
|
||||
private Long scheduleId;
|
||||
private String doctor;
|
||||
private Long doctorId;
|
||||
@@ -22,6 +23,13 @@ public class TicketSlotDTO {
|
||||
private Long patientId;
|
||||
private String phone;
|
||||
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;
|
||||
|
||||
@@ -37,4 +37,21 @@ public interface SchedulePoolMapper extends BaseMapper<SchedulePool> {
|
||||
AND p.delete_flag = '0'
|
||||
""")
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.openhis.appointmentmanage.domain.ScheduleSlot;
|
||||
import com.openhis.appointmentmanage.domain.TicketSlotDTO;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.openhis.appointmentmanage.dto.TicketQueryDTO;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.springframework.stereotype.Repository;
|
||||
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);
|
||||
|
||||
/**
|
||||
* 更新槽位状态并记录签到时间
|
||||
*
|
||||
* @param slotId 槽位ID
|
||||
* @param status 状态
|
||||
* @param checkInTime 签到时间
|
||||
* @return 结果
|
||||
*/
|
||||
int updateSlotStatusAndCheckInTime(@Param("slotId") Long slotId, @Param("status") Integer status, @Param("checkInTime") Date checkInTime);
|
||||
|
||||
/**
|
||||
* 根据槽位ID查询所属号源池ID。
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,7 @@ import lombok.experimental.Accessors;
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class Order extends HisBaseEntity {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
@TableId(type = IdType.AUTO)
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long id;
|
||||
|
||||
|
||||
@@ -32,4 +32,14 @@ public interface OrderMapper extends BaseMapper<Order> {
|
||||
int updateOrderStatusById(Long id, Integer status);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
}
|
||||
|
||||
Order order = new Order();
|
||||
order.setId(null); // 显式置空,确保触发数据库自增,避免 MP 预分配雪花 ID 的干扰
|
||||
String orderNo = assignSeqUtil.getSeq(AssignSeqEnum.ORDER_NUM.getPrefix(), 18);
|
||||
order.setOrderNo(orderNo);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.openhis.appointmentmanage.domain.AppointmentConfig;
|
||||
import com.openhis.appointmentmanage.service.IAppointmentConfigService;
|
||||
import com.openhis.appointmentmanage.domain.TicketSlotDTO;
|
||||
import com.openhis.appointmentmanage.domain.ScheduleSlot;
|
||||
import com.openhis.appointmentmanage.mapper.SchedulePoolMapper;
|
||||
import com.openhis.appointmentmanage.mapper.ScheduleSlotMapper;
|
||||
import com.openhis.clinical.domain.Order;
|
||||
@@ -52,6 +53,9 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
||||
@Resource
|
||||
private SchedulePoolMapper schedulePoolMapper;
|
||||
|
||||
@Resource
|
||||
private com.openhis.clinical.mapper.OrderMapper orderMapper;
|
||||
|
||||
@Resource
|
||||
private IAppointmentConfigService appointmentConfigService;
|
||||
|
||||
@@ -146,7 +150,25 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
||||
|
||||
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);
|
||||
|
||||
if (slot == null) {
|
||||
@@ -242,26 +264,11 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
||||
throw new RuntimeException("当前号源没有可取消的预约订单");
|
||||
}
|
||||
|
||||
// 核心逻辑:获取订单信息并检查机构取消限制
|
||||
// 获取订单信息
|
||||
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) {
|
||||
orderService.cancelAppointmentOrder(order.getId(), "患者取消预约");
|
||||
}
|
||||
@@ -274,7 +281,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
||||
}
|
||||
|
||||
/**
|
||||
* 取号
|
||||
* 取号(签到)
|
||||
*
|
||||
* @param slotId 槽位ID
|
||||
* @return 结果
|
||||
@@ -287,7 +294,24 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
||||
throw new RuntimeException("当前号源没有可取号的预约订单");
|
||||
}
|
||||
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(), "医生停诊");
|
||||
}
|
||||
|
||||
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.STOPPED);
|
||||
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.CANCELLED);
|
||||
if (updated > 0) {
|
||||
refreshPoolStatsBySlotId(slotId);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.Date;
|
||||
* @date 2025-06-03
|
||||
*/
|
||||
@Data
|
||||
@TableName("doc_ital_signs")
|
||||
@TableName("doc_vital_signs")
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class VitalSigns extends HisBaseEntity {
|
||||
|
||||
@@ -46,16 +46,24 @@ public class PaymentRecStaticServiceImpl extends ServiceImpl<PaymentRecStaticMap
|
||||
Map<String, List<ChargeItemDefInfo>> collect;
|
||||
List<PaymentRecStatic> paymentRecStatics = new ArrayList<>();
|
||||
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);
|
||||
} 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);
|
||||
} 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,
|
||||
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,
|
||||
paymentRecStatics);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ public class OpSchedule extends HisBaseEntity {
|
||||
/** 就诊ID */
|
||||
private Long visitId;
|
||||
|
||||
/** 就诊卡号 */
|
||||
@TableField(exist = false)
|
||||
private String identifierNo;
|
||||
|
||||
/** 手术编码 */
|
||||
private String operCode;
|
||||
|
||||
|
||||
@@ -4,10 +4,46 @@
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<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 实体类 -->
|
||||
<select id="selectAllTicketSlots" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
|
||||
SELECT
|
||||
s.id AS slotId,
|
||||
s.seq_no AS seqNo,
|
||||
p.schedule_id AS scheduleId,
|
||||
p.doctor_name AS doctor,
|
||||
p.dept_id AS departmentId,
|
||||
@@ -16,12 +52,12 @@
|
||||
o.patient_name AS patientName,
|
||||
o.medical_card AS medicalCard,
|
||||
o.phone AS phone,
|
||||
o.status AS orderStatus,
|
||||
s.status AS slotStatus,
|
||||
<include refid="orderStatusNormExpr" /> AS orderStatus,
|
||||
<include refid="slotStatusNormExpr" /> AS slotStatus,
|
||||
s.expect_time AS expectTime,
|
||||
p.schedule_date AS scheduleDate,
|
||||
d.reg_type AS regType,
|
||||
p.status AS poolStatus,
|
||||
<include refid="poolStatusNormExpr" /> AS poolStatus,
|
||||
p.stop_reason AS stopReason,
|
||||
d.is_stopped AS isStopped
|
||||
FROM
|
||||
@@ -39,7 +75,7 @@
|
||||
FROM
|
||||
order_main
|
||||
WHERE
|
||||
status IN (1, 2)
|
||||
LOWER(CONCAT('', status)) IN ('1', '2', '4', 'booked', 'checked', 'checked_in', 'checkin', 'returned')
|
||||
ORDER BY
|
||||
slot_id,
|
||||
create_time DESC
|
||||
@@ -56,6 +92,7 @@
|
||||
<select id="selectTicketSlotById" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
|
||||
SELECT
|
||||
s.id AS slotId,
|
||||
s.seq_no AS seqNo,
|
||||
p.schedule_id AS scheduleId,
|
||||
p.doctor_name AS doctor,
|
||||
p.doctor_id AS doctorId,
|
||||
@@ -66,12 +103,18 @@
|
||||
o.patient_name AS patientName,
|
||||
o.medical_card AS medicalCard,
|
||||
o.phone AS phone,
|
||||
o.status AS orderStatus,
|
||||
s.status AS slotStatus,
|
||||
o.id AS orderId,
|
||||
o.order_no AS orderNo,
|
||||
COALESCE(CAST(o.gender AS VARCHAR), CAST(pinfo.gender_enum AS VARCHAR)) AS patientGender,
|
||||
pinfo.gender_enum AS genderEnum,
|
||||
pinfo.id_card AS idCard,
|
||||
o.appointment_time AS appointmentTime,
|
||||
<include refid="orderStatusNormExpr" /> AS orderStatus,
|
||||
<include refid="slotStatusNormExpr" /> AS slotStatus,
|
||||
s.expect_time AS expectTime,
|
||||
p.schedule_date AS scheduleDate,
|
||||
d.reg_type AS regType,
|
||||
p.status AS poolStatus,
|
||||
<include refid="poolStatusNormExpr" /> AS poolStatus,
|
||||
p.stop_reason AS stopReason,
|
||||
d.is_stopped AS isStopped
|
||||
FROM
|
||||
@@ -87,15 +130,20 @@
|
||||
patient_name,
|
||||
medical_card,
|
||||
phone,
|
||||
id,
|
||||
order_no,
|
||||
gender,
|
||||
appointment_time,
|
||||
status
|
||||
FROM
|
||||
order_main
|
||||
WHERE
|
||||
status IN (1, 2)
|
||||
LOWER(CONCAT('', status)) IN ('1', '2', '4', 'booked', 'checked', 'checked_in', 'checkin', 'returned')
|
||||
ORDER BY
|
||||
slot_id,
|
||||
create_time DESC
|
||||
) o ON o.slot_id = s.id
|
||||
LEFT JOIN adm_patient pinfo ON o.patient_id = pinfo.id
|
||||
WHERE
|
||||
s.id = #{id}
|
||||
</select>
|
||||
@@ -115,7 +163,7 @@
|
||||
UPDATE adm_schedule_slot
|
||||
SET
|
||||
status = #{status},
|
||||
<if test="status == 0">
|
||||
<if test="status != null and '0'.equals(status.toString())">
|
||||
order_id = NULL,
|
||||
</if>
|
||||
update_time = now()
|
||||
@@ -124,10 +172,24 @@
|
||||
AND delete_flag = '0'
|
||||
</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 pool_id
|
||||
FROM adm_schedule_slot
|
||||
WHERE id = #{slotId}
|
||||
SELECT
|
||||
pool_id
|
||||
FROM
|
||||
adm_schedule_slot
|
||||
WHERE
|
||||
id = #{slotId}
|
||||
AND delete_flag = '0'
|
||||
</select>
|
||||
|
||||
@@ -145,6 +207,7 @@
|
||||
<select id="selectTicketSlotsPage" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
|
||||
SELECT
|
||||
s.id AS slotId,
|
||||
s.seq_no AS seqNo,
|
||||
p.schedule_id AS scheduleId,
|
||||
p.doctor_name AS doctor,
|
||||
p.doctor_id AS doctorId,
|
||||
@@ -155,12 +218,18 @@
|
||||
o.patient_name AS patientName,
|
||||
o.medical_card AS medicalCard,
|
||||
o.phone AS phone,
|
||||
o.status AS orderStatus,
|
||||
s.status AS slotStatus,
|
||||
o.id AS orderId,
|
||||
o.order_no AS orderNo,
|
||||
COALESCE(CAST(o.gender AS VARCHAR), CAST(pinfo.gender_enum AS VARCHAR)) AS patientGender,
|
||||
pinfo.gender_enum AS genderEnum,
|
||||
pinfo.id_card AS idCard,
|
||||
o.appointment_time AS appointmentTime,
|
||||
<include refid="orderStatusNormExpr" /> AS orderStatus,
|
||||
<include refid="slotStatusNormExpr" /> AS slotStatus,
|
||||
s.expect_time AS expectTime,
|
||||
p.schedule_date AS scheduleDate,
|
||||
d.reg_type AS regType,
|
||||
p.status AS poolStatus,
|
||||
<include refid="poolStatusNormExpr" /> AS poolStatus,
|
||||
p.stop_reason AS stopReason,
|
||||
d.is_stopped AS isStopped
|
||||
FROM
|
||||
@@ -176,15 +245,20 @@
|
||||
patient_name,
|
||||
medical_card,
|
||||
phone,
|
||||
id,
|
||||
order_no,
|
||||
gender,
|
||||
appointment_time,
|
||||
status
|
||||
FROM
|
||||
order_main
|
||||
WHERE
|
||||
status IN (1, 2)
|
||||
LOWER(CONCAT('', status)) IN ('1', '2', '4', 'booked', 'checked', 'checked_in', 'checkin', 'returned')
|
||||
ORDER BY
|
||||
slot_id,
|
||||
create_time DESC
|
||||
) o ON o.slot_id = s.id
|
||||
LEFT JOIN adm_patient pinfo ON o.patient_id = pinfo.id
|
||||
<where>
|
||||
p.delete_flag = '0'
|
||||
AND s.delete_flag = '0' <!-- 1. 按日期查 -->
|
||||
@@ -225,35 +299,49 @@
|
||||
<!-- 5. 核心:解答您疑问的 4 种业务状态的复合查询! -->
|
||||
<if test="query.status != null and query.status != '' and query.status != 'all'">
|
||||
<choose>
|
||||
<when test="query.status == 'unbooked'">
|
||||
AND s.status = 0
|
||||
<when test="'unbooked'.equals(query.status) or '未预约'.equals(query.status)">
|
||||
AND <include refid="slotStatusNormExpr" /> = 0
|
||||
AND (
|
||||
d.is_stopped IS NULL
|
||||
OR d.is_stopped = FALSE
|
||||
)
|
||||
</when>
|
||||
<when test="query.status == 'booked'">
|
||||
AND s.status = 1
|
||||
AND o.status = 1
|
||||
<when test="'booked'.equals(query.status) or '已预约'.equals(query.status)">
|
||||
AND <include refid="slotStatusNormExpr" /> = 1
|
||||
AND <include refid="orderStatusNormExpr" /> = 1
|
||||
AND (
|
||||
d.is_stopped IS NULL
|
||||
OR d.is_stopped = FALSE
|
||||
)
|
||||
</when>
|
||||
<when test="query.status == 'checked'">
|
||||
AND s.status = 1
|
||||
AND o.status = 2
|
||||
<when test="'checked'.equals(query.status) or '已取号'.equals(query.status)">
|
||||
AND (
|
||||
<include refid="slotStatusNormExpr" /> = 4
|
||||
OR (
|
||||
<include refid="slotStatusNormExpr" /> = 1
|
||||
AND <include refid="orderStatusNormExpr" /> = 2
|
||||
)
|
||||
)
|
||||
AND (
|
||||
d.is_stopped IS NULL
|
||||
OR d.is_stopped = FALSE
|
||||
)
|
||||
</when>
|
||||
<when test="query.status == 'cancelled'">
|
||||
<when test="'cancelled'.equals(query.status) or '已停诊'.equals(query.status) or '已取消'.equals(query.status)">
|
||||
AND (
|
||||
s.status = 2
|
||||
<include refid="slotStatusNormExpr" /> = 2
|
||||
OR d.is_stopped = TRUE
|
||||
)
|
||||
</when>
|
||||
<when test="'returned'.equals(query.status) or '已退号'.equals(query.status)">
|
||||
AND (
|
||||
<include refid="slotStatusNormExpr" /> = 5
|
||||
OR <include refid="orderStatusNormExpr" /> = 4
|
||||
)
|
||||
</when>
|
||||
<otherwise>
|
||||
AND 1 = 2
|
||||
</otherwise>
|
||||
</choose>
|
||||
</if>
|
||||
</where>
|
||||
@@ -266,9 +354,22 @@
|
||||
SELECT
|
||||
p.doctor_id AS doctorId,
|
||||
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
|
||||
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'
|
||||
END AS ticketType
|
||||
FROM
|
||||
@@ -290,7 +391,10 @@
|
||||
AND d.reg_type = 1
|
||||
</when>
|
||||
<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>
|
||||
</choose>
|
||||
</if>
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
select * from order_main where slot_id = #{slotId} and status = 1
|
||||
</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
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<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>
|
||||
|
||||
<update id="updatePayStatus">
|
||||
update order_main
|
||||
set pay_status = #{payStatus}, pay_time = #{payTime}, update_time = NOW()
|
||||
where id = #{orderId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteOrderById">
|
||||
delete from order_main where id = #{id}
|
||||
</delete>
|
||||
|
||||
@@ -29,10 +29,17 @@ export function formatDateStr(cellValue, format = 'YYYY-MM-DD HH:mm:ss') {
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
|
||||
// 支持不带前导零的格式
|
||||
const monthNoPad = String(date.getMonth() + 1);
|
||||
const dayNoPad = String(date.getDate());
|
||||
|
||||
return format
|
||||
.replace('YYYY', year)
|
||||
.replace('MM', month)
|
||||
.replace('DD', day)
|
||||
.replace('M/', monthNoPad + '/') // 支持 M/ 格式
|
||||
.replace('D ', dayNoPad + ' ') // 支持 D 格式(后跟空格)
|
||||
.replace('/D', '/' + dayNoPad) // 支持 /D 格式
|
||||
.replace('HH', hours)
|
||||
.replace('mm', minutes)
|
||||
.replace('ss', seconds);
|
||||
|
||||
@@ -162,3 +162,61 @@ export const STATUS = {
|
||||
NORMAL: '0', // 正常/启用
|
||||
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';
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<option value="booked">已预约</option>
|
||||
<option value="checked">已取号</option>
|
||||
<option value="cancelled">已停诊</option>
|
||||
<option value="returned">已退号</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="patientSearch" class="patient-search">
|
||||
@@ -105,7 +106,7 @@
|
||||
<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 class="ticket-index">{{ index + 1 }}</div>
|
||||
<div class="ticket-index">{{ item.seqNo != null ? item.seqNo : index + 1 }}</div>
|
||||
<!-- 1.时间 -->
|
||||
<div class="ticket-id-time">{{ item.dateTime }}</div>
|
||||
<!-- 2. 状态标签 -->
|
||||
@@ -125,7 +126,7 @@
|
||||
<div class="ticket-type">{{ item.ticketType === 'general' ? '普通' : '专家' }}</div>
|
||||
<!-- 7. 已预约患者信息 -->
|
||||
<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 class="ticket-actions">
|
||||
<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>{{ 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>{{ patient.idCard }}</td>
|
||||
<td>{{ patient.phone }}</td>
|
||||
@@ -254,6 +256,7 @@ const STATUS_CLASS_MAP = {
|
||||
'未预约': 'status-unbooked',
|
||||
'已预约': 'status-booked',
|
||||
'已取号': 'status-checked',
|
||||
'已退号': 'status-returned',
|
||||
'已停诊': 'status-cancelled',
|
||||
'已取消': 'status-cancelled'
|
||||
};
|
||||
@@ -364,14 +367,23 @@ export default {
|
||||
this.searchPatients();
|
||||
},
|
||||
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) {
|
||||
const normalizedKeyword = String(keyword || '').trim().toLowerCase();
|
||||
if (!normalizedKeyword) {
|
||||
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));
|
||||
},
|
||||
searchPatients() {
|
||||
@@ -394,6 +406,8 @@ export default {
|
||||
const rowKey = this.getPatientUniqueId(patient, index);
|
||||
return {
|
||||
...patient,
|
||||
// 统一口径:预约场景的就诊卡号使用患者标识表中的 identifierNo
|
||||
medicalCard: patient.identifierNo || patient.medicalCard || '',
|
||||
_rowKey: rowKey
|
||||
};
|
||||
});
|
||||
@@ -494,6 +508,7 @@ export default {
|
||||
this.tickets[ticketIndex].patientName = null;
|
||||
this.tickets[ticketIndex].patientId = null;
|
||||
this.tickets[ticketIndex].patientGender = null;
|
||||
this.tickets[ticketIndex].gender = null;
|
||||
this.tickets[ticketIndex].medicalCard = null;
|
||||
this.tickets[ticketIndex].phone = null;
|
||||
}
|
||||
@@ -554,12 +569,22 @@ export default {
|
||||
|
||||
try {
|
||||
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 = {
|
||||
ticketId: 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,
|
||||
medicalCard: this.selectedPatient.medicalCard || this.selectedPatient.id,
|
||||
medicalCard,
|
||||
phone: this.selectedPatient.phone,
|
||||
gender: this.getGenderValueForBackend(this.selectedPatient),
|
||||
fee: Number(this.currentTicket.fee) || 0,
|
||||
@@ -578,8 +603,9 @@ export default {
|
||||
if (ticketIndex !== -1) {
|
||||
this.tickets[ticketIndex].status = '已预约';
|
||||
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].gender = this.getGenderText(this.tickets[ticketIndex].patientGender);
|
||||
}
|
||||
|
||||
this.closePatientSelectModal();
|
||||
@@ -601,9 +627,9 @@ export default {
|
||||
},
|
||||
// 获取性别文本
|
||||
getGenderText(genderValue) {
|
||||
// 如果值为null或undefined,返回'-'
|
||||
// 如果值为null或undefined,返回"未知"
|
||||
if (genderValue === null || genderValue === undefined) {
|
||||
return '-';
|
||||
return '未知';
|
||||
}
|
||||
|
||||
// 将值转换为字符串进行比较
|
||||
@@ -623,8 +649,8 @@ export default {
|
||||
return '女';
|
||||
}
|
||||
|
||||
// 如果都不是,返回原值或'-'
|
||||
return '-';
|
||||
// 如果都不是,返回"未知"
|
||||
return '未知';
|
||||
},
|
||||
// 获取用于后端的性别值
|
||||
getGenderValueForBackend(patient) {
|
||||
@@ -703,10 +729,36 @@ export default {
|
||||
return;
|
||||
}
|
||||
const records = payload.list || payload.records || [];
|
||||
const filteredRecords = this.applyStatusFilter(records);
|
||||
const total = Number(payload.total);
|
||||
this.tickets = [...records];
|
||||
this.allTickets = [...records];
|
||||
this.tickets = [...filteredRecords];
|
||||
this.allTickets = [...filteredRecords];
|
||||
// 当按状态筛选时,优先使用前端过滤后的数量,避免后端状态未生效导致“显示全部”
|
||||
if (this.selectedStatus && this.selectedStatus !== 'all') {
|
||||
this.totalTickets = this.tickets.length;
|
||||
} else {
|
||||
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) {
|
||||
let doctorList = [];
|
||||
@@ -1376,6 +1428,11 @@ export default {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.status-returned {
|
||||
background-color: #fff7e6;
|
||||
color: #d46b08;
|
||||
}
|
||||
|
||||
.status-cancelled {
|
||||
background-color: #fff1f0;
|
||||
color: #ff4d4f;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<div style="display: flex; align-items: center; width: 100%">
|
||||
<span style="font-size: 16px; font-weight: bold; margin-right: 20px;">门诊挂号</span>
|
||||
<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="Plus" @click="handleAddPatient" 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="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="success" icon="Printer" @click="handleReprint" size="small">补打挂号</el-button>
|
||||
<el-button type="info" icon="Printer" @click="handleReprint" size="small">补打挂号</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
@@ -632,7 +701,8 @@ import {
|
||||
returnRegister,
|
||||
updatePatientPhone,
|
||||
} from './components/outpatientregistration';
|
||||
import {invokeYbPlugin5000, invokeYbPlugin5001} from '@/api/public';
|
||||
import { listTicket, checkInTicket } from '@/api/appoinmentmanage/ticket';
|
||||
import { invokeYbPlugin5000, invokeYbPlugin5001 } from '@/api/public';
|
||||
import patientInfoDialog from './components/patientInfoDialog';
|
||||
import PatientAddDialog from './components/patientAddDialog';
|
||||
import patientList from './components/patientList';
|
||||
@@ -644,7 +714,7 @@ import {handleColor} from '@/utils/his';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import {formatDateStr} from '@/utils/index';
|
||||
import {isValidCNPhoneNumber} from '../../../utils/validate';
|
||||
import {ElMessage} from 'element-plus';
|
||||
import {ElMessage, ElMessageBox} from 'element-plus';
|
||||
import {hiprint} from 'vue-plugin-hiprint';
|
||||
import outpatientRegistrationTemplate from '@/components/Print/OutpatientRegistration.json';
|
||||
|
||||
@@ -687,14 +757,25 @@ const ybTypeRef = ref(null);
|
||||
const openDialog = ref(false);
|
||||
const openRefundDialog = ref(false);
|
||||
const openReprintDialog = ref(false);
|
||||
|
||||
// 预约签到相关变量
|
||||
const showCheckInPatientModal = ref(false);
|
||||
const checkInPatientList = ref([]);
|
||||
const selectedCheckInPatient = ref(null);
|
||||
const totalAmount = ref(0);
|
||||
const chargeItemIdList = ref([]);
|
||||
const chrgBchnoList = ref([]);
|
||||
const paymentId = 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 queryType = ref('all'); // 查询类型:all-全部, normal-正常挂号, returned-退号记录
|
||||
const guardianAgeConfig = ref(''); // 监护人规定年龄配置
|
||||
const currentSlotId = ref(null); // 当前预约签到的号源ID
|
||||
|
||||
// 使用 ref 定义查询所得用户信息数据
|
||||
const patientInfoList = ref(undefined);
|
||||
@@ -1584,6 +1665,189 @@ function handleReprint() {
|
||||
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. 设置 patientInfo(ChargeDialog 需要展示)
|
||||
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 的 contractNo,ChargeDialog 的 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('操作成功');
|
||||
// 更新患者手机号
|
||||
updatePhone();
|
||||
// getList();
|
||||
// reset();
|
||||
// addOutpatientRegistration(transformedData.value).then((response) => {
|
||||
// reset();
|
||||
// proxy.$modal.msgSuccess('新增成功');
|
||||
// getList();
|
||||
// });
|
||||
|
||||
// 先取出并清空,避免接口失败/取消等路径导致 slotId 残留污染下一单
|
||||
const pendingSlotId = currentSlotId.value;
|
||||
currentSlotId.value = null;
|
||||
|
||||
// 如果是预约签到的挂号,执行签到状态更新
|
||||
if (pendingSlotId) {
|
||||
checkInTicket(pendingSlotId).then(() => {
|
||||
console.log('预约状态已更新为已取号');
|
||||
}).catch(err => {
|
||||
console.error('更新预约状态失败:', err);
|
||||
ElMessage.error('预约状态更新失败,请手动签到');
|
||||
});
|
||||
}
|
||||
} else if (value == 'cancel') {
|
||||
currentSlotId.value = null;
|
||||
// cancelRegister(patientInfo.value.encounterId).then((res) => {
|
||||
// if (res.code == 200) {
|
||||
// getList();
|
||||
// }
|
||||
// });
|
||||
} else {
|
||||
currentSlotId.value = null;
|
||||
openRefundDialog.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +273,10 @@ function fetchFromApi(searchKey) {
|
||||
minUnitCode_dictText: item.minUnitCode_dictText || item.unitCode_dictText || '',
|
||||
volume: item.size || item.totalVolume || '',
|
||||
partPercent: item.partPercent || 1,
|
||||
inventoryList: [],
|
||||
// 🔧 Bug Fix: 如果后端提供了inventoryList,则使用;否则为空数组
|
||||
inventoryList: item.inventoryList || [],
|
||||
// 🔧 Bug Fix: 构造stockList用于库存显示
|
||||
stockList: item.inventoryList || [],
|
||||
adviceDefinitionId: item.id,
|
||||
chargeItemDefinitionId: item.id,
|
||||
positionId: item.locationId,
|
||||
@@ -354,6 +357,11 @@ function handleQuantity(row) {
|
||||
const totalQuantity = row.inventoryList.reduce((sum, item) => sum + (item.quantity || 0), 0);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -280,6 +280,7 @@ import {
|
||||
saveDiagnosis,
|
||||
} from '../api';
|
||||
import { DIAG_TYPE } from '@/utils/medicalConstants';
|
||||
import { formatDateStr } from '@/utils';
|
||||
import diagnosisdialog from '../diagnosis/diagnosisdialog.vue';
|
||||
import AddDiagnosisDialog from './addDiagnosisDialog.vue';
|
||||
import diagnosislist from '../diagnosis/diagnosislist.vue';
|
||||
@@ -628,11 +629,11 @@ async function handleSaveDiagnosis() {
|
||||
// 开始加载状态,防止重复提交
|
||||
saveLoading.value = true;
|
||||
|
||||
// 保存前按排序号排序,并转换日期格式为ISO字符串
|
||||
// 保存前按排序号排序,并转换日期格式为后端期望的格式 yyyy/M/d HH:mm:ss
|
||||
const diagnosisChildList = form.value.diagnosisList.map(item => ({
|
||||
...item,
|
||||
onsetDate: item.onsetDate ? new Date(item.onsetDate).toISOString() : null,
|
||||
diagnosisTime: item.diagnosisTime ? new Date(item.diagnosisTime).toISOString() : null
|
||||
onsetDate: item.onsetDate ? formatDateStr(item.onsetDate, 'YYYY/M/D HH:mm:ss') : null,
|
||||
diagnosisTime: item.diagnosisTime ? formatDateStr(item.diagnosisTime, 'YYYY/M/D HH:mm:ss') : null
|
||||
}));
|
||||
|
||||
// 调用保存诊断接口
|
||||
@@ -657,7 +658,9 @@ async function handleSaveDiagnosis() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存诊断失败:', error);
|
||||
proxy.$modal.msgError('保存诊断失败,请稍后重试');
|
||||
// 显示后端返回的具体错误信息
|
||||
const errorMsg = error?.response?.data?.msg || error?.message || '保存诊断失败,请稍后重试';
|
||||
proxy.$modal.msgError(errorMsg);
|
||||
} finally {
|
||||
// 结束加载状态
|
||||
saveLoading.value = false;
|
||||
|
||||
@@ -164,7 +164,14 @@
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<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-col>
|
||||
</el-row>
|
||||
@@ -308,6 +315,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Printer, Delete } from '@element-plus/icons-vue';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import request from '@/utils/request';
|
||||
import { listCheckMethod } from '@/api/system/checkType';
|
||||
|
||||
const props = defineProps({
|
||||
patientInfo: { type: Object, default: () => ({}) },
|
||||
@@ -373,10 +381,69 @@ const categoryList = ref([]); // 原始分类+项目数据
|
||||
const dictSearchKey = 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 () => {
|
||||
await loadAllMethods();
|
||||
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 } // 取全量分类数据
|
||||
});
|
||||
let types = [];
|
||||
if (typeRes.data?.records) types = typeRes.data.records;
|
||||
else if (Array.isArray(typeRes.data)) types = typeRes.data;
|
||||
else if (Array.isArray(typeRes.rows)) types = typeRes.rows;
|
||||
if (typeRes && typeRes.data) {
|
||||
if (Array.isArray(typeRes.data)) {
|
||||
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. 加载检查项目(检查部位项目)
|
||||
const partRes = await request({ url: '/check/part/list', method: 'get' });
|
||||
let parts = [];
|
||||
if (Array.isArray(partRes)) parts = partRes;
|
||||
else if (Array.isArray(partRes.data?.data)) parts = partRes.data.data; // 双层嵌套:{ data: { data: [...] } }
|
||||
else if (Array.isArray(partRes.data)) parts = partRes.data;
|
||||
else if (Array.isArray(partRes.rows)) parts = partRes.rows;
|
||||
else if (partRes.data?.records) parts = partRes.data.records;
|
||||
if (partRes && partRes.data) {
|
||||
if (Array.isArray(partRes.data)) {
|
||||
parts = partRes.data;
|
||||
} else if (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 归类
|
||||
const dict = [];
|
||||
for (const t of types) {
|
||||
dict.push({
|
||||
typeId: t.id,
|
||||
typeCode: t.type,
|
||||
typeCode: t.code, // 保存 code 用于后备匹配
|
||||
orgType: t.type, // 保存 type 用于后备匹配
|
||||
typeName: t.name, // 保存 name
|
||||
categoryName: t.name,
|
||||
items: []
|
||||
});
|
||||
@@ -425,7 +512,15 @@ async function loadCategoryList() {
|
||||
nationalCode: p.nationalCode || '',
|
||||
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);
|
||||
else unclassified.push(mapped);
|
||||
}
|
||||
|
||||
@@ -546,6 +546,11 @@
|
||||
expandOrder = [];
|
||||
// 当医嘱类型改变时,清空当前选择的项目名称,因为不同类型项目的数据结构可能不兼容
|
||||
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(复数)
|
||||
|
||||
// 根据选择的类型设置categoryCode,用于药品分类筛选
|
||||
@@ -956,14 +961,18 @@ const { method_code, unit_code, rate_code, distribution_category_code, drord_doc
|
||||
// drord_doctor_type: 1=西药, 2=中成药, 3=诊疗, 4=耗材, 5=会诊, 6=手术
|
||||
const adviceTypeList = computed(() => {
|
||||
// 如果字典已加载,使用字典数据;否则使用默认值
|
||||
let list = [];
|
||||
if (drord_doctor_type.value && drord_doctor_type.value.length > 0) {
|
||||
return drord_doctor_type.value.map(item => ({
|
||||
// 过滤掉字典中已有的"全部"选项,避免重复
|
||||
list = drord_doctor_type.value
|
||||
.filter(item => item.label !== '全部')
|
||||
.map(item => ({
|
||||
label: item.label,
|
||||
value: parseInt(item.value) || item.value
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
// 默认返回值,确保页面正常显示
|
||||
return [
|
||||
list = [
|
||||
{ label: '西药', value: 1 },
|
||||
{ label: '中成药', value: 2 },
|
||||
{ label: '诊疗', value: 3 },
|
||||
@@ -971,10 +980,19 @@ const adviceTypeList = computed(() => {
|
||||
{ label: '会诊', value: 5 },
|
||||
{ label: '手术', value: 6 },
|
||||
];
|
||||
}
|
||||
// 在最后添加"全部"选项
|
||||
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);
|
||||
return found ? found.label : '';
|
||||
};
|
||||
@@ -1602,11 +1620,8 @@ function getListInfo(addNewRow) {
|
||||
// 🔧 Bug Fix: 后端保存时将耗材(4)转换为中成药(2),显示时需要转换回来
|
||||
// 检查 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) {
|
||||
@@ -2343,6 +2358,13 @@ function handleSave(prescriptionId) {
|
||||
item.accountId = finalAccountId;
|
||||
}
|
||||
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;
|
||||
@@ -2366,20 +2388,25 @@ function handleSave(prescriptionId) {
|
||||
finalUnitCode = item.minUnitCode;
|
||||
}
|
||||
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 {
|
||||
item.minUnitQuantity = item.quantity;
|
||||
}
|
||||
|
||||
// --- 【修改点3:Bug 1 修复 (类型转换)】 ---
|
||||
let saveAdviceType = item.adviceType;
|
||||
if (item.adviceType == 4) {
|
||||
saveAdviceType = 2; // 耗材:前端4 -> 后端2
|
||||
} else if (item.adviceType == 2) {
|
||||
saveAdviceType = 1; // 中成药:前端2 -> 后端1
|
||||
} else if (item.adviceType == 5) {
|
||||
// 🔧 Bug Fix: 保持原类型,不进行转换
|
||||
// 前端类型: 1=西药, 2=中成药, 3=诊疗, 4=耗材, 5=会诊, 6=手术
|
||||
// 后端类型: 1=药品, 2=耗材, 3=医疗活动, 6=手术
|
||||
// 后端会通过 ItemType.DEVICE.getValue() || adviceType == 4 来识别耗材
|
||||
if (item.adviceType == 5) {
|
||||
saveAdviceType = 3; // 会诊:前端5 -> 后端3(诊疗类)
|
||||
} else if (item.adviceType == 6) {
|
||||
saveAdviceType = 6; // 🔧 BugFix#318: 手术类型保持为6
|
||||
}
|
||||
|
||||
// 🔧 Bug Fix: Validate and fix NaN values before sending to backend
|
||||
@@ -2439,6 +2466,8 @@ function handleSave(prescriptionId) {
|
||||
quantity: finalQuantity,
|
||||
unitCode: finalUnitCode,
|
||||
totalPrice: item.totalPrice, // Ensure totalPrice is valid
|
||||
// 🔧 Bug Fix: 确保 categoryEnum 被传递(耗材必填字段)
|
||||
categoryEnum: item.categoryEnum || parsedContent.categoryEnum,
|
||||
// 🔧 Bug Fix: 确保库存匹配成功的关键字段
|
||||
adviceTableName: adviceTableNameVal,
|
||||
locationId: locationIdVal,
|
||||
@@ -2446,6 +2475,13 @@ function handleSave(prescriptionId) {
|
||||
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,
|
||||
@@ -2635,12 +2671,17 @@ function handleOrderBindInfo(bindIdInfo, currentMethodCode) {
|
||||
encounterDiagnosisId: encounterDiagnosisId.value,
|
||||
// 🔧 确保 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);
|
||||
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.totalPrice = (item.quantity * newRow.minUnitPrice).toFixed(6);
|
||||
newRow.minUnitQuantity = item.quantity;
|
||||
@@ -2705,6 +2746,12 @@ function handleSaveSign(row, index, prescriptionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔧 Bug Fix: 验证医嘱类型不能为"全部"
|
||||
if (row.adviceType === 0) {
|
||||
proxy.$modal.msgWarning('请选择医嘱类型');
|
||||
return;
|
||||
}
|
||||
|
||||
// 重新查找索引,确保使用当前处方列表中的正确索引
|
||||
const actualIndex = prescriptionList.value.findIndex(item => item.uniqueKey === row.uniqueKey);
|
||||
if (actualIndex === -1) {
|
||||
@@ -2774,14 +2821,18 @@ function handleSaveSign(row, index, prescriptionId) {
|
||||
row.encounterId = props.patientInfo.encounterId;
|
||||
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) {
|
||||
row.minUnitQuantity =
|
||||
row.minUnitCode == row.unitCode ? row.quantity : row.quantity * row.partPercent;
|
||||
} else {
|
||||
// 🔧 Bug Fix: 耗材和其他类型,minUnitQuantity等于quantity
|
||||
row.minUnitQuantity = row.quantity;
|
||||
// 🔧 Bug Fix: 确保minUnitCode等于unitCode(耗材只有一个单位)
|
||||
row.minUnitCode = row.unitCode;
|
||||
row.minUnitCode_dictText = row.unitCode_dictText;
|
||||
}
|
||||
|
||||
row.conditionId = conditionId.value;
|
||||
@@ -2976,14 +3027,12 @@ function handleSaveBatch(prescriptionId) {
|
||||
|
||||
// --- Bug 1 修复:类型转换 ---
|
||||
let saveAdviceType = item.adviceType;
|
||||
if (item.adviceType == 4) {
|
||||
saveAdviceType = 2; // 耗材前端4 -> 后端2
|
||||
} else if (item.adviceType == 2) {
|
||||
saveAdviceType = 1; // 中成药前端2 -> 后端1
|
||||
} else if (item.adviceType == 5) {
|
||||
// 🔧 Bug Fix: 保持原类型,不进行转换
|
||||
// 前端类型: 1=西药, 2=中成药, 3=诊疗, 4=耗材, 5=会诊, 6=手术
|
||||
// 后端类型: 1=药品, 2=耗材, 3=医疗活动, 6=手术
|
||||
// 后端会通过 ItemType.DEVICE.getValue() || adviceType == 4 来识别耗材
|
||||
if (item.adviceType == 5) {
|
||||
saveAdviceType = 3; // 会诊前端5 -> 后端3(诊疗类)
|
||||
} else if (item.adviceType == 6) {
|
||||
saveAdviceType = 6; // 🔧 BugFix#318: 手术类型保持为6
|
||||
}
|
||||
|
||||
// 🔧 BugFix#318: 过滤掉手术特有字段,只保留标准医嘱字段
|
||||
@@ -2994,7 +3043,11 @@ function handleSaveBatch(prescriptionId) {
|
||||
'encounterId', 'groupId', 'injectFlag', 'lotNumber', 'methodCode', 'partPercent',
|
||||
'patientId', 'positionId', 'positionName', 'prescriptionNo', 'quantity', 'rateCode',
|
||||
'requestId', 'skinTestFlag', 'sortNumber', 'statusEnum', 'totalPrice',
|
||||
'unitCode', 'unitPrice', 'volume', 'ybClassEnum'
|
||||
'unitCode', 'unitPrice', 'volume', 'ybClassEnum',
|
||||
// 🔧 Bug Fix: 添加 definitionId 和 definitionDetailId 字段
|
||||
'definitionId', 'definitionDetailId',
|
||||
// 🔧 Bug Fix: 添加 categoryEnum 字段(耗材必填)
|
||||
'categoryEnum'
|
||||
];
|
||||
let filteredItem = {};
|
||||
standardItemFields.forEach(field => {
|
||||
@@ -3193,6 +3246,14 @@ function syncGroupFields(row) {
|
||||
function setValue(row) {
|
||||
unitCodeList.value = [];
|
||||
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({
|
||||
value: row.doseUnitCode,
|
||||
label: row.doseUnitCode_dictText,
|
||||
@@ -3204,6 +3265,7 @@ function setValue(row) {
|
||||
label: row.minUnitCode_dictText,
|
||||
type: 'minUnit',
|
||||
});
|
||||
}
|
||||
if (row.adviceType == 2 && row.minUnitCode != row.unitCode) {
|
||||
unitCodeList.value.push({
|
||||
value: row.minUnitCode,
|
||||
@@ -3237,8 +3299,15 @@ function setValue(row) {
|
||||
prescriptionList.value[rowIndex.value].unitCodeList = unitCodeList.value;
|
||||
prescriptionList.value[rowIndex.value].doseUnitCode = row.doseUnitCode;
|
||||
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 =
|
||||
row.partAttributeEnum == 1 ? row.minUnitCode : row.unitCode;
|
||||
}
|
||||
prescriptionList.value[rowIndex.value].categoryEnum = row.categoryCode;
|
||||
prescriptionList.value[rowIndex.value].skinTestFlag = row.skinTestFlag;
|
||||
prescriptionList.value[rowIndex.value].definitionId = row.chargeItemDefinitionId;
|
||||
@@ -3307,6 +3376,10 @@ function setValue(row) {
|
||||
// 🔧 Bug #218 修复:保留组套中的quantity,如果没有则默认1
|
||||
prescriptionList.value[rowIndex.value].quantity = 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 || '';
|
||||
// 🔧 Bug Fix: 使用 positionId,如果为空则使用患者信息中的 orgId
|
||||
console.log('设置耗材locationId:', {
|
||||
@@ -3325,6 +3398,10 @@ function setValue(row) {
|
||||
prescriptionList.value[rowIndex.value].minUnitPrice = 0;
|
||||
prescriptionList.value[rowIndex.value].quantity = row.quantity || 1;
|
||||
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 || '';
|
||||
const finalLocationId = row.positionId || props.patientInfo.orgId;
|
||||
prescriptionList.value[rowIndex.value].locationId = finalLocationId;
|
||||
@@ -3967,6 +4044,16 @@ function convertDoseValues(row, index) {
|
||||
// 总量计算,仅适用只有两种单位的情况
|
||||
function calculateTotalAmount(row, index) {
|
||||
nextTick(() => {
|
||||
// 🔧 Bug Fix: 处理耗材类型的总金额计算
|
||||
if (row.adviceType == 4) {
|
||||
row.totalPrice = (row.quantity * row.unitPrice).toFixed(6);
|
||||
// 🔧 Bug Fix: 确保耗材的minUnitQuantity和minUnitCode正确设置
|
||||
row.minUnitQuantity = row.quantity;
|
||||
row.minUnitCode = row.unitCode;
|
||||
row.minUnitCode_dictText = row.unitCode_dictText;
|
||||
return;
|
||||
}
|
||||
|
||||
if (row.adviceType == 2) {
|
||||
calculateTotalPrice(row, index);
|
||||
return;
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="卫生机构" align="center" prop="orgName" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="姓名" align="center" prop="patientName" width="100" />
|
||||
<el-table-column label="就诊卡号" align="center" prop="patientCardNo" width="120" />
|
||||
<el-table-column label="就诊卡号" align="center" prop="identifierNo" width="120" />
|
||||
<el-table-column label="手术名称" align="center" prop="operName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="申请科室" align="center" prop="applyDeptName" width="100" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
@@ -161,9 +161,9 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="就诊卡号" prop="patientCardNo">
|
||||
<el-tooltip :content="form.patientCardNo" placement="top" :disabled="!form.patientCardNo">
|
||||
<el-input v-model="form.patientCardNo" :disabled="true" style="width: 100%" />
|
||||
<el-form-item label="就诊卡号" prop="identifierNo">
|
||||
<el-tooltip :content="form.identifierNo" placement="top" :disabled="!form.identifierNo">
|
||||
<el-input v-model="form.identifierNo" :disabled="true" style="width: 100%" />
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -900,6 +900,7 @@ const form = reactive({
|
||||
applyId: undefined,
|
||||
patientId: undefined,
|
||||
visitId: undefined,
|
||||
identifierNo: undefined,
|
||||
operCode: undefined,
|
||||
operName: undefined,
|
||||
preoperativeDiagnosis: undefined,
|
||||
@@ -1783,7 +1784,7 @@ function confirmApply() {
|
||||
form.applyId=selectedRow.applyId// 手术申请id
|
||||
form.patientId = selectedRow.patientId// 患者id
|
||||
form.visitId = selectedRow.encounterId // id对应填入就诊id
|
||||
form.patientCardNo = selectedRow.patientCardNo // 就诊卡号
|
||||
form.identifierNo = selectedRow.identifierNo || '' // 就诊卡号
|
||||
form.operCode = selectedRow.surgeryNo // 手术单号作为手术编码
|
||||
form.operName = selectedRow.descJson?.surgeryName//手术名称
|
||||
form.preoperativeDiagnosis = selectedRow.preoperativeDiagnosis || selectedRow.descJson?.preoperativeDiagnosis
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="patient-info-content">
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="患者:">{{ patientInfo.patientName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="就诊卡号:">{{ patientInfo.visitId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="就诊卡号:">{{ patientInfo.identifierNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手术单号:">{{ patientInfo.operCode || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="科室:">{{ patientInfo.roomCode || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="医师:">{{ patientInfo.doctorName || '-' }}</el-descriptions-item>
|
||||
|
||||
Reference in New Issue
Block a user