Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
2026-02-06 12:35:43 +08:00
25 changed files with 4225 additions and 25 deletions

View File

@@ -0,0 +1,89 @@
package com.openhis.web.consultation.appservice;
import com.openhis.web.consultation.dto.ConsultationActivityDto;
import com.openhis.web.consultation.dto.ConsultationRequestDto;
import com.openhis.web.consultation.dto.DepartmentTreeDto;
import java.util.List;
import java.util.Map;
/**
* 会诊管理AppService接口
*
* @author system
* @date 2026-01-29
*/
public interface IConsultationAppService {
/**
* 获取会诊列表
*
* @param encounterId 就诊ID可选如果为null则查询当前医生的所有会诊申请
* @return 会诊列表
*/
List<ConsultationRequestDto> getConsultationList(Long encounterId);
/**
* 保存会诊申请
*
* @param dto 会诊申请DTO
* @return 是否成功
*/
Boolean saveConsultation(ConsultationRequestDto dto);
/**
* 提交会诊申请
*
* @param consultationId 会诊申请单号
* @return 是否成功
*/
Boolean submitConsultation(String consultationId);
/**
* 作废会诊申请
*
* @param consultationId 会诊申请单号
* @param cancelReason 作废原因
* @return 是否成功
*/
Boolean cancelConsultation(String consultationId, String cancelReason);
/**
* 结束会诊申请
*
* @param consultationId 会诊申请单号
* @return 是否成功
*/
Boolean completeConsultation(String consultationId);
/**
* 获取科室医生树
*
* @return 科室医生树
*/
List<DepartmentTreeDto> getDepartmentTree();
/**
* 获取主诊断
*
* @param encounterId 就诊ID
* @return 主诊断信息
*/
Map<String, String> getMainDiagnosis(Long encounterId);
/**
* 获取我的会诊邀请列表(被邀请的医生查看)
*
* @return 会诊邀请列表
*/
List<ConsultationRequestDto> getMyInvitations();
/**
* 获取所有会诊项目及价格
*
* @return 会诊项目列表
*/
List<ConsultationActivityDto> getConsultationActivities();
}

View File

@@ -0,0 +1,967 @@
package com.openhis.web.consultation.appservice.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.common.utils.SecurityUtils;
import com.openhis.administration.domain.Encounter;
import com.openhis.administration.domain.EncounterDiagnosis;
import com.openhis.administration.domain.Organization;
import com.openhis.administration.domain.Patient;
import com.openhis.administration.domain.Practitioner;
import com.openhis.administration.mapper.EncounterDiagnosisMapper;
import com.openhis.administration.mapper.EncounterMapper;
import com.openhis.administration.mapper.OrganizationMapper;
import com.openhis.administration.mapper.PatientMapper;
import com.openhis.administration.mapper.PractitionerMapper;
import com.openhis.clinical.domain.Condition;
import com.openhis.clinical.mapper.ConditionMapper;
import com.openhis.workflow.domain.ServiceRequest;
import com.openhis.workflow.service.IServiceRequestService;
import com.openhis.common.enums.RequestStatus;
import com.openhis.common.enums.GenerateSource;
import com.openhis.common.enums.ItemType;
import com.core.common.utils.AssignSeqUtil;
import com.openhis.common.enums.AssignSeqEnum;
import com.openhis.web.consultation.appservice.IConsultationAppService;
import com.openhis.web.consultation.dto.ConsultationActivityDto;
import com.openhis.web.consultation.dto.ConsultationRequestDto;
import com.openhis.web.consultation.dto.DepartmentTreeDto;
import com.openhis.web.consultation.dto.InvitedObjectDto;
import com.openhis.web.consultation.dto.PhysicianNodeDto;
import com.openhis.web.consultation.mapper.ConsultationRequestMapper;
import com.openhis.web.consultation.mapper.ConsultationInvitedMapper;
import com.openhis.web.consultation.domain.ConsultationRequest;
import com.openhis.web.consultation.domain.ConsultationInvited;
import com.openhis.web.consultation.enums.ConsultationStatusEnum;
import com.openhis.web.consultation.enums.InvitedStatusEnum;
import com.openhis.web.consultation.enums.ConsultationUrgencyEnum;
import com.openhis.workflow.domain.ActivityDefinition;
import com.openhis.workflow.mapper.ActivityDefinitionMapper;
import com.openhis.administration.domain.ChargeItemDefinition;
import com.openhis.administration.mapper.ChargeItemDefinitionMapper;
import com.openhis.administration.domain.ChargeItem;
import com.openhis.administration.service.IChargeItemService;
import com.openhis.common.enums.ChargeItemStatus;
import com.openhis.web.doctorstation.mapper.DoctorStationAdviceAppMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* 会诊管理AppService实现类
*
* @author system
* @date 2026-01-29
*/
@Slf4j
@Service
public class ConsultationAppServiceImpl implements IConsultationAppService {
@Resource
private ConsultationRequestMapper consultationRequestMapper;
@Resource
private ConsultationInvitedMapper consultationInvitedMapper;
@Resource
private EncounterMapper encounterMapper;
@Resource
private PatientMapper patientMapper;
@Resource
private ConditionMapper conditionMapper;
@Resource
private OrganizationMapper organizationMapper;
@Resource
private PractitionerMapper practitionerMapper;
@Resource
private EncounterDiagnosisMapper encounterDiagnosisMapper;
@Resource
private IServiceRequestService iServiceRequestService;
@Resource
private AssignSeqUtil assignSeqUtil;
@Resource
private ActivityDefinitionMapper activityDefinitionMapper;
@Resource
private ChargeItemDefinitionMapper chargeItemDefinitionMapper;
@Resource
private IChargeItemService iChargeItemService;
@Resource
private DoctorStationAdviceAppMapper doctorStationAdviceAppMapper;
/**
* 用于生成唯一序列号的计数器
* 每秒重置,范围 0000-9999
*/
private static final AtomicInteger SEQUENCE = new AtomicInteger(0);
/**
* 记录上一次生成ID的时间秒级
*/
private static volatile long lastSecond = 0L;
@Override
public List<ConsultationRequestDto> getConsultationList(Long encounterId) {
// 查询会诊列表
LambdaQueryWrapper<ConsultationRequest> wrapper = new LambdaQueryWrapper<>();
// 根据就诊ID查询该患者的会诊申请
wrapper.eq(ConsultationRequest::getEncounterId, encounterId);
wrapper.orderByDesc(ConsultationRequest::getCreateTime);
List<ConsultationRequest> list = consultationRequestMapper.selectList(wrapper);
// 转换为DTO
return list.stream().map(this::convertToDto).collect(Collectors.toList());
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean saveConsultation(ConsultationRequestDto dto) {
try {
// 判断是新增还是更新
boolean isUpdate = (dto.getId() != null);
ConsultationRequest entity;
if (isUpdate) {
// 更新:查询现有记录
entity = consultationRequestMapper.selectById(dto.getId());
if (entity == null) {
throw new RuntimeException("会诊申请记录不存在ID: " + dto.getId());
}
} else {
// 新增:创建新记录
entity = new ConsultationRequest();
entity.setConsultationId(generateConsultationId());
entity.setTenantId(SecurityUtils.getLoginUser().getTenantId().longValue());
entity.setConsultationRequestDate(new Date());
}
// 复制基本属性(现在字段名已统一,可以直接复制)
BeanUtils.copyProperties(dto, entity, "id", "consultationId", "invitedList", "submitFlag", "provisionalDiagnosis", "consultationRequestDate");
// 如果前端没有传递申请医生ID使用当前登录用户
if (entity.getRequestingPhysicianId() == null) {
entity.setRequestingPhysicianId(SecurityUtils.getLoginUser().getPractitionerId());
}
// 设置邀请对象文本(拼接所有医生名称)
if (dto.getInvitedList() != null && !dto.getInvitedList().isEmpty()) {
String invitedObjectText = dto.getInvitedList().stream()
.map(InvitedObjectDto::getPhysicianName)
.collect(Collectors.joining(","));
entity.setInvitedObject(invitedObjectText);
}
// 处理门诊诊断:如果前端没有传递,自动从数据库获取
if (!StringUtils.hasText(dto.getProvisionalDiagnosis()) && dto.getEncounterId() != null) {
String diagnosis = getPatientDiagnosis(dto.getEncounterId());
if (StringUtils.hasText(diagnosis)) {
entity.setProvisionalDiagnosis(diagnosis);
}
} else {
entity.setProvisionalDiagnosis(dto.getProvisionalDiagnosis());
}
// 设置会诊状态(使用枚举)
if (Boolean.TRUE.equals(dto.getSubmitFlag())) {
entity.setConsultationStatus(ConsultationStatusEnum.SUBMITTED.getCode());
entity.setConfirmingPhysician(SecurityUtils.getLoginUser().getUser().getNickName());
entity.setConfirmingPhysicianId(SecurityUtils.getLoginUser().getPractitionerId());
entity.setConfirmingDate(new Date());
} else {
entity.setConsultationStatus(ConsultationStatusEnum.NEW.getCode());
}
// 保存或更新主表
if (isUpdate) {
consultationRequestMapper.updateById(entity);
// 删除旧的邀请对象
LambdaQueryWrapper<ConsultationInvited> deleteWrapper = new LambdaQueryWrapper<>();
deleteWrapper.eq(ConsultationInvited::getConsultationRequestId, entity.getId());
consultationInvitedMapper.delete(deleteWrapper);
// 更新门诊医嘱表
if (entity.getOrderId() != null) {
updateConsultationServiceRequest(entity);
}
} else {
consultationRequestMapper.insert(entity);
// 保存到门诊医嘱表
saveConsultationServiceRequest(entity);
}
// 保存邀请对象到关联表
if (dto.getInvitedList() != null && !dto.getInvitedList().isEmpty()) {
for (InvitedObjectDto invitedDto : dto.getInvitedList()) {
ConsultationInvited invited = new ConsultationInvited();
invited.setConsultationRequestId(entity.getId());
invited.setInvitedDepartmentId(invitedDto.getDeptId());
invited.setInvitedDepartmentName(invitedDto.getDeptName());
invited.setInvitedPhysicianId(invitedDto.getPhysicianId());
invited.setInvitedPhysicianName(invitedDto.getPhysicianName());
invited.setInvitedStatus(InvitedStatusEnum.PENDING.getCode());
invited.setTenantId(entity.getTenantId());
consultationInvitedMapper.insert(invited);
}
}
return true;
} catch (Exception e) {
log.error("保存会诊申请失败", e);
throw new RuntimeException("保存会诊申请失败: " + e.getMessage());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean submitConsultation(String consultationId) {
try {
if (!StringUtils.hasText(consultationId)) {
throw new IllegalArgumentException("会诊申请单号不能为空");
}
LambdaQueryWrapper<ConsultationRequest> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ConsultationRequest::getConsultationId, consultationId);
ConsultationRequest entity = consultationRequestMapper.selectOne(wrapper);
if (entity == null) {
throw new IllegalArgumentException("会诊申请不存在");
}
// 只有新开状态才能提交
if (!ConsultationStatusEnum.NEW.getCode().equals(entity.getConsultationStatus())) {
throw new IllegalArgumentException("只有新开状态的会诊申请才能提交");
}
// 更新状态为已提交
entity.setConsultationStatus(ConsultationStatusEnum.SUBMITTED.getCode());
// 设置提交会诊的医生信息
entity.setConfirmingPhysician(SecurityUtils.getLoginUser().getUser().getNickName());
entity.setConfirmingPhysicianId(SecurityUtils.getLoginUser().getPractitionerId());
entity.setConfirmingDate(new Date());
consultationRequestMapper.updateById(entity);
// 新增:更新门诊医嘱表状态为已提交
updateServiceRequestStatus(entity.getOrderId(), RequestStatus.ACTIVE.getValue());
return true;
} catch (Exception e) {
log.error("提交会诊申请失败", e);
throw new RuntimeException("提交会诊申请失败: " + e.getMessage());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean cancelConsultation(String consultationId, String cancelReason) {
try {
if (!StringUtils.hasText(consultationId)) {
throw new IllegalArgumentException("会诊申请单号不能为空");
}
LambdaQueryWrapper<ConsultationRequest> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ConsultationRequest::getConsultationId, consultationId);
ConsultationRequest entity = consultationRequestMapper.selectOne(wrapper);
if (entity == null) {
throw new IllegalArgumentException("会诊申请不存在");
}
// 判断是"取消提交"还是"作废"
if ("取消提交".equals(cancelReason) && ConsultationStatusEnum.SUBMITTED.getCode().equals(entity.getConsultationStatus())) {
// 取消提交:将状态从"已提交"改回"新开"
entity.setConsultationStatus(ConsultationStatusEnum.NEW.getCode());
entity.setConfirmingPhysician(null);
entity.setConfirmingPhysicianId(null);
entity.setConfirmingDate(null);
consultationRequestMapper.updateById(entity);
// 更新门诊医嘱表状态为新开
updateServiceRequestStatus(entity.getOrderId(), RequestStatus.DRAFT.getValue());
} else {
// 作废:将状态改为"已取消"
entity.setConsultationStatus(ConsultationStatusEnum.CANCELLED.getCode());
entity.setCancelReason(cancelReason);
entity.setCancelNatureDate(new Date());
consultationRequestMapper.updateById(entity);
// 更新门诊医嘱表状态为已作废
updateServiceRequestStatus(entity.getOrderId(), RequestStatus.CANCELLED.getValue());
}
return true;
} catch (Exception e) {
log.error("操作失败", e);
throw new RuntimeException("操作失败: " + e.getMessage());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean completeConsultation(String consultationId) {
try {
if (!StringUtils.hasText(consultationId)) {
throw new IllegalArgumentException("会诊申请单号不能为空");
}
LambdaQueryWrapper<ConsultationRequest> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ConsultationRequest::getConsultationId, consultationId);
ConsultationRequest entity = consultationRequestMapper.selectOne(wrapper);
if (entity == null) {
throw new IllegalArgumentException("会诊申请不存在");
}
// 更新状态为已完成
entity.setConsultationStatus(ConsultationStatusEnum.COMPLETED.getCode());
consultationRequestMapper.updateById(entity);
// 🎯 新增:更新门诊医嘱表状态为已完成
updateServiceRequestStatus(entity.getOrderId(), RequestStatus.COMPLETED.getValue());
return true;
} catch (Exception e) {
log.error("结束会诊申请失败", e);
throw new RuntimeException("结束会诊申请失败: " + e.getMessage());
}
}
@Override
public List<DepartmentTreeDto> getDepartmentTree() {
try {
// 查询所有科室
LambdaQueryWrapper<Organization> deptWrapper = new LambdaQueryWrapper<>();
deptWrapper.eq(Organization::getDeleteFlag, "0")
.orderByAsc(Organization::getName);
List<Organization> deptList = organizationMapper.selectList(deptWrapper);
// 查询所有医生
LambdaQueryWrapper<Practitioner> practitionerWrapper = new LambdaQueryWrapper<>();
practitionerWrapper.eq(Practitioner::getDeleteFlag, "0")
.orderByAsc(Practitioner::getName);
List<Practitioner> practitionerList = practitionerMapper.selectList(practitionerWrapper);
// 按科室分组医生
Map<Long, List<Practitioner>> practitionerMap = practitionerList.stream()
.filter(p -> p.getOrgId() != null)
.collect(Collectors.groupingBy(Practitioner::getOrgId));
// 构建树形结构
List<DepartmentTreeDto> treeList = new ArrayList<>();
for (Organization dept : deptList) {
DepartmentTreeDto treeDto = new DepartmentTreeDto();
treeDto.setId(dept.getId());
treeDto.setLabel(dept.getName());
// 添加该科室的医生
List<Practitioner> physicians = practitionerMap.get(dept.getId());
if (physicians != null && !physicians.isEmpty()) {
List<PhysicianNodeDto> children = physicians.stream()
.map(p -> {
PhysicianNodeDto node = new PhysicianNodeDto();
node.setId(p.getId());
node.setLabel(p.getName());
return node;
})
.collect(Collectors.toList());
treeDto.setChildren(children);
} else {
treeDto.setChildren(new ArrayList<>());
}
treeList.add(treeDto);
}
return treeList;
} catch (Exception e) {
log.error("获取科室医生树失败", e);
throw new RuntimeException("获取科室医生树失败: " + e.getMessage());
}
}
@Override
public Map<String, String> getMainDiagnosis(Long encounterId) {
Map<String, String> result = new HashMap<>();
if (encounterId == null) {
return result;
}
try {
String diagnosis = getPatientDiagnosis(encounterId);
if (StringUtils.hasText(diagnosis)) {
result.put("diagnosis", diagnosis);
}
return result;
} catch (Exception e) {
log.error("获取主诊断失败", e);
return result;
}
}
/**
* 获取患者诊断信息
*
* @param encounterId 就诊ID
* @return 诊断信息字符串
*/
private String getPatientDiagnosis(Long encounterId) {
try {
// 查询该就诊的所有诊断,优先查询主诊断
LambdaQueryWrapper<EncounterDiagnosis> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(EncounterDiagnosis::getEncounterId, encounterId)
.orderByDesc(EncounterDiagnosis::getMaindiseFlag) // 主诊断排在前面
.orderByAsc(EncounterDiagnosis::getDiagSrtNo) // 按诊断排序号排序
.orderByDesc(EncounterDiagnosis::getCreateTime); // 按创建时间倒序
List<EncounterDiagnosis> diagnosisList = encounterDiagnosisMapper.selectList(wrapper);
if (diagnosisList == null || diagnosisList.isEmpty()) {
return null;
}
// 使用 LinkedHashSet 去重,保持顺序
LinkedHashSet<String> diagnosisSet = new LinkedHashSet<>();
int mainDiagnosisCount = 0;
for (EncounterDiagnosis ed : diagnosisList) {
// 优先使用 name 字段,如果没有则使用 diagnosisDesc
String diagName = StringUtils.hasText(ed.getName()) ? ed.getName() : ed.getDiagnosisDesc();
if (StringUtils.hasText(diagName)) {
// 构建诊断文本
StringBuilder itemBuilder = new StringBuilder();
itemBuilder.append(diagName);
// 如果有诊断描述且与名称不同,添加到括号中
if (StringUtils.hasText(ed.getDiagnosisDesc()) &&
!ed.getDiagnosisDesc().equals(diagName)) {
itemBuilder.append("(").append(ed.getDiagnosisDesc()).append(")");
}
// 标记主诊断
if (ed.getMaindiseFlag() != null && ed.getMaindiseFlag() == 1) {
itemBuilder.append("[主]");
mainDiagnosisCount++;
}
// 添加到集合(自动去重)
diagnosisSet.add(itemBuilder.toString());
// 限制最多显示 10 个诊断(包括主诊断)
if (diagnosisSet.size() >= 10) {
break;
}
}
}
// 拼接诊断信息
String result = String.join("", diagnosisSet);
return result;
} catch (Exception e) {
log.error("获取患者诊断信息失败encounterId: {}", encounterId, e);
return null;
}
}
@Override
public List<ConsultationRequestDto> getMyInvitations() {
try {
// 获取当前登录医生ID
Long currentPhysicianId = SecurityUtils.getLoginUser().getPractitionerId();
// 查询邀请我的会诊申请
LambdaQueryWrapper<ConsultationInvited> invitedWrapper = new LambdaQueryWrapper<>();
invitedWrapper.eq(ConsultationInvited::getInvitedPhysicianId, currentPhysicianId)
.orderByDesc(ConsultationInvited::getCreateTime);
List<ConsultationInvited> invitedList = consultationInvitedMapper.selectList(invitedWrapper);
if (invitedList.isEmpty()) {
return new ArrayList<>();
}
// 获取所有会诊申请ID
List<Long> requestIds = invitedList.stream()
.map(ConsultationInvited::getConsultationRequestId)
.distinct()
.collect(Collectors.toList());
// 查询会诊申请详情
List<ConsultationRequest> requests = consultationRequestMapper.selectBatchIds(requestIds);
// 转换为DTO并添加邀请状态
Map<Long, ConsultationInvited> invitedMap = invitedList.stream()
.collect(Collectors.toMap(ConsultationInvited::getConsultationRequestId, v -> v, (v1, v2) -> v1));
return requests.stream().map(request -> {
ConsultationRequestDto dto = convertToDto(request);
// 添加我的邀请状态
ConsultationInvited myInvited = invitedMap.get(request.getId());
if (myInvited != null) {
dto.setMyInvitedStatus(myInvited.getInvitedStatus());
dto.setMyConfirmTime(myInvited.getConfirmTime());
}
return dto;
}).collect(Collectors.toList());
} catch (Exception e) {
log.error("获取我的会诊邀请失败", e);
throw new RuntimeException("获取我的会诊邀请失败: " + e.getMessage());
}
}
/**
* 转换为DTO
*/
private ConsultationRequestDto convertToDto(ConsultationRequest entity) {
ConsultationRequestDto dto = new ConsultationRequestDto();
BeanUtils.copyProperties(entity, dto);
// 查询邀请对象列表
if (entity.getId() != null) {
LambdaQueryWrapper<ConsultationInvited> invitedWrapper = new LambdaQueryWrapper<>();
invitedWrapper.eq(ConsultationInvited::getConsultationRequestId, entity.getId());
List<ConsultationInvited> invitedList = consultationInvitedMapper.selectList(invitedWrapper);
if (invitedList != null && !invitedList.isEmpty()) {
List<InvitedObjectDto> invitedDtoList = invitedList.stream().map(invited -> {
InvitedObjectDto invitedDto = new InvitedObjectDto();
invitedDto.setDeptId(invited.getInvitedDepartmentId());
invitedDto.setDeptName(invited.getInvitedDepartmentName());
invitedDto.setPhysicianId(invited.getInvitedPhysicianId());
invitedDto.setPhysicianName(invited.getInvitedPhysicianName());
return invitedDto;
}).collect(Collectors.toList());
dto.setInvitedList(invitedDtoList);
}
}
// 如果门诊诊断为空,尝试从数据库自动获取
if (!StringUtils.hasText(entity.getProvisionalDiagnosis()) && entity.getEncounterId() != null) {
String diagnosis = getPatientDiagnosis(entity.getEncounterId());
if (StringUtils.hasText(diagnosis)) {
dto.setProvisionalDiagnosis(diagnosis);
}
}
return dto;
}
/**
* 生成会诊申请单号(高性能版本)
* 格式CS + 年月日时分秒 + 4位序列号
* 例如CS202601301425300001
*
* 优化说明:
* 1. 使用 AtomicInteger 保证线程安全,性能远高于 synchronized
* 2. 每秒最多支持 10000 个并发请求0000-9999
* 3. 无需查询数据库,性能提升 100+ 倍
* 4. 秒级时间戳 + 自增序列号,确保唯一性
*/
private String generateConsultationId() {
while (true) {
long nowMillis = System.currentTimeMillis();
long currentSecond = nowMillis / 1000;
// 跨秒则重置序列号(双重检查 + 类锁)
if (currentSecond != lastSecond) {
synchronized (ConsultationAppServiceImpl.class) {
if (currentSecond != lastSecond) {
lastSecond = currentSecond;
SEQUENCE.set(0);
}
}
}
int sequence = SEQUENCE.getAndIncrement();
// 本秒内序列号耗尽精确等待到下一秒再重试避免递归、避免固定100ms
if (sequence >= 10000) {
// 可选“回退自增”让 SEQUENCE 不乱飙
// 回退这次自增(可选,但更“稳”,避免 SEQUENCE 无限增长)
// 注意:回退可能在极端并发下导致轻微争用,但比不回退更可控
SEQUENCE.decrementAndGet();
sleepUntilNextSecond(nowMillis);
continue;
}
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyyMMddHHmmss");
String dateTime = sdf.format(new Date());
String sequenceStr = String.format("%04d", sequence);
return "CS" + dateTime + sequenceStr;
}
}
// 精确等到下一秒,等待更短、抖动更小,不在固定睡100ms
private void sleepUntilNextSecond(long nowMillis) {
long nextSecondMillis = (nowMillis / 1000 + 1) * 1000;
long sleepMillis = nextSecondMillis - nowMillis;
// 防御:理论上 sleepMillis 范围是 1~1000ms
if (sleepMillis <= 0) return;
try {
Thread.sleep(sleepMillis);
} catch (InterruptedException e) {
// 恢复中断标记:把“有人希望我停下来”的信号传递给上层
Thread.currentThread().interrupt();
throw new RuntimeException("生成会诊申请单号失败:线程被中断", e);
}
}
// private String generateConsultationId() {
// java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyyMMddHHmmss");
// long currentSecond = System.currentTimeMillis() / 1000;
//
// // 如果是新的一秒,重置序列号
// // 跨秒检测:如果发现当前秒变了,说明进入下一秒
// // 重置序列号:把 SEQUENCE 重新设置为 0保证新的一秒从 0000 开始
// // 双重锁检测:避免重复进入
// if (currentSecond != lastSecond) {
// synchronized (ConsultationAppServiceImpl.class) {
// if (currentSecond != lastSecond) {
// lastSecond = currentSecond;
// SEQUENCE.set(0);
// }
// }
// }
//
// // 获取当前序列号并自增(线程安全)
// int sequence = SEQUENCE.getAndIncrement();
//
// // 如果同一秒内超过 10000 个请求,等待下一秒
// if (sequence >= 10000) {
// try {
// Thread.sleep(100); // 等待 100ms
// return generateConsultationId(); // 递归重试
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// throw new RuntimeException("生成会诊申请单号失败", e);
// }
// }
//
// // 生成时间戳部分
// String dateTime = sdf.format(new Date());
//
// // 生成4位序列号补零
// String sequenceStr = String.format("%04d", sequence);
//
// return "CS" + dateTime + sequenceStr;
// }
/**
* 计算年龄
*/
private Integer calculateAge(Date birthDate) {
if (birthDate == null) {
return null;
}
java.util.Calendar now = java.util.Calendar.getInstance();
java.util.Calendar birth = java.util.Calendar.getInstance();
birth.setTime(birthDate);
int age = now.get(java.util.Calendar.YEAR) - birth.get(java.util.Calendar.YEAR);
if (now.get(java.util.Calendar.DAY_OF_YEAR) < birth.get(java.util.Calendar.DAY_OF_YEAR)) {
age--;
}
return age;
}
/**
* 保存会诊医嘱到门诊医嘱表wor_service_request
* 同时创建费用项adm_charge_item
*
* @param consultationRequest 会诊申请实体
*/
private void saveConsultationServiceRequest(ConsultationRequest consultationRequest) {
try {
ServiceRequest serviceRequest = new ServiceRequest();
// 生成医嘱编号
String busNo = assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4);
serviceRequest.setBusNo(busNo);
// 基本信息
serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
// 🎯 使用前端选择的会诊项目ID
Long activityDefinitionId = consultationRequest.getConsultationActivityId();
if (activityDefinitionId == null) {
// 如果前端没有传递,使用默认的"院内会诊"
activityDefinitionId = 1983417749497446402L;
log.warn("前端未传递会诊项目ID使用默认的院内会诊: {}", activityDefinitionId);
}
// 🎯 设置医嘱类型为会诊31=会诊类型)
// 医嘱类型分类1-10=药品22-30=诊疗31=会诊40+=其他
serviceRequest.setCategoryEnum(31); // 会诊类型
serviceRequest.setActivityId(activityDefinitionId); // 关联到会诊项目定义
serviceRequest.setPatientId(consultationRequest.getPatientId());
serviceRequest.setEncounterId(consultationRequest.getEncounterId());
serviceRequest.setRequesterId(consultationRequest.getRequestingPhysicianId());
// 🎯 设置执行科室(申请医生的科室)
serviceRequest.setOrgId(consultationRequest.getDepartmentId());
// 🎯 设置执行次数为 1
serviceRequest.setQuantity(BigDecimal.ONE);
serviceRequest.setUnitCode("111"); // 单位:次
// 医嘱状态:新开
serviceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());
// 医嘱内容保存会诊申请的详细信息JSON格式
Map<String, Object> contentMap = new HashMap<>();
contentMap.put("consultationRequestId", consultationRequest.getId());
contentMap.put("consultationId", consultationRequest.getConsultationId());
contentMap.put("consultationType", consultationRequest.getConsultationActivityName());
contentMap.put("consultationActivityId", activityDefinitionId);
contentMap.put("patientName", consultationRequest.getPatientName());
contentMap.put("provisionalDiagnosis", consultationRequest.getProvisionalDiagnosis());
contentMap.put("consultationPurpose", consultationRequest.getConsultationPurpose());
contentMap.put("requestingPhysician", consultationRequest.getRequestingPhysician());
contentMap.put("department", consultationRequest.getDepartment());
contentMap.put("adviceName", consultationRequest.getConsultationActivityName()); // 添加项目名称
serviceRequest.setContentJson(JSON.toJSONString(contentMap));
// 时间信息
serviceRequest.setAuthoredTime(new Date());
// 审计字段
serviceRequest.setTenantId(SecurityUtils.getLoginUser().getTenantId().intValue());
serviceRequest.setCreateBy(SecurityUtils.getUsername());
serviceRequest.setCreateTime(new Date());
// 保存到数据库
iServiceRequestService.save(serviceRequest);
// 🎯 新增创建费用项ChargeItem
// 查询会诊项目的价格定义
LambdaQueryWrapper<ChargeItemDefinition> priceWrapper = new LambdaQueryWrapper<>();
priceWrapper.eq(ChargeItemDefinition::getInstanceTable, "wor_activity_definition")
.eq(ChargeItemDefinition::getInstanceId, activityDefinitionId)
.eq(ChargeItemDefinition::getStatusEnum, 2) // 2=启用
.eq(ChargeItemDefinition::getDeleteFlag, "0")
.last("LIMIT 1");
ChargeItemDefinition chargeItemDef = chargeItemDefinitionMapper.selectOne(priceWrapper);
if (chargeItemDef != null) {
// 🎯 查询患者的费用性质account_id
// 从 adm_account 表获取患者的 account_id费用性质
Long accountId = doctorStationAdviceAppMapper.getEncounterContract(consultationRequest.getEncounterId())
.stream()
.findFirst()
.map(contract -> contract.getAccountId())
.orElse(null);
if (accountId == null) {
log.warn("未找到患者的费用性质encounterId: {}", consultationRequest.getEncounterId());
throw new RuntimeException("未找到患者的费用性质信息,无法创建费用项");
}
ChargeItem chargeItem = new ChargeItem();
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 草稿状态
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(busNo));
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
chargeItem.setPatientId(consultationRequest.getPatientId());
chargeItem.setContextEnum(ItemType.ACTIVITY.getValue()); // 诊疗类型
chargeItem.setEncounterId(consultationRequest.getEncounterId());
// 🎯 设置费用性质(必填字段)
chargeItem.setAccountId(accountId);
// 费用定价信息
chargeItem.setDefinitionId(chargeItemDef.getId());
chargeItem.setUnitPrice(chargeItemDef.getPrice());
chargeItem.setQuantityValue(BigDecimal.ONE);
chargeItem.setQuantityUnit("111"); // 次
chargeItem.setTotalPrice(chargeItemDef.getPrice());
// 关联医嘱信息
chargeItem.setServiceTable("wor_service_request");
chargeItem.setServiceId(serviceRequest.getId());
chargeItem.setProductTable("wor_activity_definition");
chargeItem.setProductId(activityDefinitionId);
// 开立信息
chargeItem.setEntererId(consultationRequest.getRequestingPhysicianId());
chargeItem.setRequestingOrgId(consultationRequest.getDepartmentId());
chargeItem.setEnteredDate(new Date());
// 审计字段
chargeItem.setTenantId(SecurityUtils.getLoginUser().getTenantId());
chargeItem.setCreateBy(SecurityUtils.getUsername());
chargeItem.setCreateTime(new Date());
iChargeItemService.save(chargeItem);
} else {
log.warn("未找到会诊项目的价格定义activityId: {}", activityDefinitionId);
}
// 将医嘱ID保存到会诊申请表
consultationRequest.setOrderId(serviceRequest.getId());
consultationRequestMapper.updateById(consultationRequest);
} catch (Exception e) {
log.error("保存会诊医嘱失败", e);
throw new RuntimeException("保存会诊医嘱失败: " + e.getMessage());
}
}
/**
* 更新会诊医嘱
*
* @param consultationRequest 会诊申请实体
*/
private void updateConsultationServiceRequest(ConsultationRequest consultationRequest) {
try {
if (consultationRequest.getOrderId() == null) {
log.warn("会诊申请没有关联的医嘱ID跳过更新");
return;
}
ServiceRequest serviceRequest = iServiceRequestService.getById(consultationRequest.getOrderId());
if (serviceRequest == null) {
log.warn("未找到关联的医嘱记录OrderId: {}", consultationRequest.getOrderId());
return;
}
// 更新医嘱内容
serviceRequest.setContentJson(JSON.toJSONString(consultationRequest));
serviceRequest.setUpdateBy(SecurityUtils.getUsername());
serviceRequest.setUpdateTime(new Date());
iServiceRequestService.updateById(serviceRequest);
} catch (Exception e) {
log.error("更新会诊医嘱失败", e);
throw new RuntimeException("更新会诊医嘱失败: " + e.getMessage());
}
}
/**
* 更新门诊医嘱状态
*
* @param orderId 医嘱ID
* @param status 新状态
*/
private void updateServiceRequestStatus(Long orderId, Integer status) {
try {
if (orderId == null) {
log.warn("医嘱ID为空跳过状态更新");
return;
}
ServiceRequest serviceRequest = iServiceRequestService.getById(orderId);
if (serviceRequest == null) {
log.warn("未找到医嘱记录OrderId: {}", orderId);
return;
}
serviceRequest.setStatusEnum(status);
serviceRequest.setUpdateBy(SecurityUtils.getUsername());
serviceRequest.setUpdateTime(new Date());
iServiceRequestService.updateById(serviceRequest);
} catch (Exception e) {
log.error("更新医嘱状态失败", e);
throw new RuntimeException("更新医嘱状态失败: " + e.getMessage());
}
}
@Override
public List<ConsultationActivityDto> getConsultationActivities() {
try {
// 查询所有会诊相关的诊疗活动
LambdaQueryWrapper<ActivityDefinition> wrapper = new LambdaQueryWrapper<>();
wrapper.like(ActivityDefinition::getName, "会诊")
.eq(ActivityDefinition::getDeleteFlag, "0")
.orderBy(true, true, ActivityDefinition::getSortOrder);
List<ActivityDefinition> activityList = activityDefinitionMapper.selectList(wrapper);
if (activityList.isEmpty()) {
return new ArrayList<>();
}
// 转换为DTO并查询价格
List<ConsultationActivityDto> resultList = new ArrayList<>();
for (ActivityDefinition activity : activityList) {
ConsultationActivityDto dto = new ConsultationActivityDto();
dto.setId(activity.getId());
dto.setName(activity.getName());
dto.setBusNo(activity.getBusNo());
dto.setTypeEnum(activity.getTypeEnum());
dto.setCategoryCode(activity.getCategoryCode());
dto.setUnitCode(activity.getPermittedUnitCode());
dto.setStatusEnum(activity.getStatusEnum());
// 查询价格
LambdaQueryWrapper<ChargeItemDefinition> priceWrapper = new LambdaQueryWrapper<>();
priceWrapper.eq(ChargeItemDefinition::getInstanceTable, "wor_activity_definition")
.eq(ChargeItemDefinition::getInstanceId, activity.getId())
.eq(ChargeItemDefinition::getStatusEnum, 2) // 2=启用
.eq(ChargeItemDefinition::getDeleteFlag, "0")
.last("LIMIT 1");
ChargeItemDefinition chargeItem = chargeItemDefinitionMapper.selectOne(priceWrapper);
if (chargeItem != null && chargeItem.getPrice() != null) {
dto.setPrice(chargeItem.getPrice());
} else {
dto.setPrice(java.math.BigDecimal.ZERO);
log.warn("会诊项目: {} 未找到价格定义", activity.getName());
}
resultList.add(dto);
}
return resultList;
} catch (Exception e) {
log.error("查询会诊项目列表失败", e);
throw new RuntimeException("查询会诊项目列表失败: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,175 @@
package com.openhis.web.consultation.controller;
import com.core.common.core.domain.R;
import com.openhis.web.consultation.appservice.IConsultationAppService;
import com.openhis.web.consultation.dto.ConsultationActivityDto;
import com.openhis.web.consultation.dto.ConsultationRequestDto;
import com.openhis.web.consultation.dto.DepartmentTreeDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* 会诊管理Controller
*
* @author system
* @date 2026-01-29
*/
@Slf4j
@Api(tags = "会诊管理")
@RestController
@RequestMapping("/consultation")
public class ConsultationController {
@Resource
private IConsultationAppService consultationAppService;
/**
* 获取会诊列表
*/
@ApiOperation("获取会诊列表")
@GetMapping("/list")
public R<List<ConsultationRequestDto>> getConsultationList(
@ApiParam("就诊ID可选不传则查询当前医生的所有会诊申请")
@RequestParam(required = false) Long encounterId) {
try {
List<ConsultationRequestDto> list = consultationAppService.getConsultationList(encounterId);
return R.ok(list);
} catch (Exception e) {
log.error("获取会诊列表失败", e);
return R.fail("获取会诊列表失败: " + e.getMessage());
}
}
/**
* 保存会诊申请
*/
@ApiOperation("保存会诊申请")
@PostMapping("/save")
public R<String> saveConsultation(@RequestBody ConsultationRequestDto dto) {
try {
Boolean result = consultationAppService.saveConsultation(dto);
return result ? R.ok("保存成功") : R.fail("保存失败");
} catch (Exception e) {
log.error("保存会诊申请失败", e);
return R.fail("保存会诊申请失败: " + e.getMessage());
}
}
/**
* 提交会诊申请
*/
@ApiOperation("提交会诊申请")
@PostMapping("/submit")
public R<String> submitConsultation(
@ApiParam("会诊申请单号") @RequestParam String consultationId) {
try {
Boolean result = consultationAppService.submitConsultation(consultationId);
return result ? R.ok("提交成功") : R.fail("提交失败");
} catch (Exception e) {
log.error("提交会诊申请失败", e);
return R.fail("提交会诊申请失败: " + e.getMessage());
}
}
/**
* 作废会诊申请
*/
@ApiOperation("作废会诊申请")
@PostMapping("/cancel")
public R<String> cancelConsultation(
@ApiParam("会诊申请单号") @RequestParam String consultationId,
@ApiParam("作废原因") @RequestParam(required = false) String cancelReason) {
try {
Boolean result = consultationAppService.cancelConsultation(consultationId, cancelReason);
return result ? R.ok("作废成功") : R.fail("作废失败");
} catch (Exception e) {
log.error("作废会诊申请失败", e);
return R.fail("作废会诊申请失败: " + e.getMessage());
}
}
/**
* 结束会诊申请
*/
@ApiOperation("结束会诊申请")
@PostMapping("/complete")
public R<String> completeConsultation(
@ApiParam("会诊申请单号") @RequestParam String consultationId) {
try {
Boolean result = consultationAppService.completeConsultation(consultationId);
return result ? R.ok("结束成功") : R.fail("结束失败");
} catch (Exception e) {
log.error("结束会诊申请失败", e);
return R.fail("结束会诊申请失败: " + e.getMessage());
}
}
/**
* 获取科室医生树
*/
@ApiOperation("获取科室医生树")
@GetMapping("/departmentTree")
public R<List<DepartmentTreeDto>> getDepartmentTree() {
try {
List<DepartmentTreeDto> tree = consultationAppService.getDepartmentTree();
return R.ok(tree);
} catch (Exception e) {
log.error("获取科室医生树失败", e);
return R.fail("获取科室医生树失败: " + e.getMessage());
}
}
/**
* 获取主诊断
*/
@ApiOperation("获取主诊断")
@GetMapping("/mainDiagnosis")
public R<Map<String, String>> getMainDiagnosis(
@ApiParam("就诊ID") @RequestParam Long encounterId) {
try {
Map<String, String> diagnosis = consultationAppService.getMainDiagnosis(encounterId);
return R.ok(diagnosis);
} catch (Exception e) {
log.error("获取主诊断失败", e);
return R.fail("获取主诊断失败: " + e.getMessage());
}
}
/**
* 获取我的会诊邀请列表
*/
@ApiOperation("获取我的会诊邀请列表")
@GetMapping("/myInvitations")
public R<List<ConsultationRequestDto>> getMyInvitations() {
try {
List<ConsultationRequestDto> list = consultationAppService.getMyInvitations();
return R.ok(list);
} catch (Exception e) {
log.error("获取我的会诊邀请失败", e);
return R.fail("获取我的会诊邀请失败: " + e.getMessage());
}
}
/**
* 获取所有会诊项目及价格
*/
@ApiOperation("获取所有会诊项目及价格")
@GetMapping("/activities")
public R<List<ConsultationActivityDto>> getConsultationActivities() {
try {
List<ConsultationActivityDto> list = consultationAppService.getConsultationActivities();
return R.ok(list);
} catch (Exception e) {
log.error("获取会诊项目列表失败", e);
return R.fail("获取会诊项目列表失败: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,116 @@
package com.openhis.web.consultation.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 会诊邀请对象实体类
*
* @author system
* @date 2026-01-30
*/
@Data
@TableName("consultation_invited")
public class ConsultationInvited implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 会诊申请ID外键
*/
@TableField("consultation_request_id")
private Long consultationRequestId;
/**
* 邀请科室ID
*/
@TableField("invited_department_id")
private Long invitedDepartmentId;
/**
* 邀请科室名称
*/
@TableField("invited_department_name")
private String invitedDepartmentName;
/**
* 邀请医生ID
*/
@TableField("invited_physician_id")
private Long invitedPhysicianId;
/**
* 邀请医生姓名
*/
@TableField("invited_physician_name")
private String invitedPhysicianName;
/**
* 邀请状态0-待确认1-已确认2-已拒绝)
*/
@TableField("invited_status")
private Integer invitedStatus;
/**
* 确认时间
*/
@TableField("confirm_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date confirmTime;
/**
* 确认意见
*/
@TableField("confirm_opinion")
private String confirmOpinion;
/**
* 创建时间
*/
@TableField(value = "create_time", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新时间
*/
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/**
* 创建人
*/
@TableField(value = "create_by", fill = FieldFill.INSERT)
private String createBy;
/**
* 更新人
*/
@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/**
* 租户ID
*/
@TableField("tenant_id")
private Long tenantId;
/**
* 删除标识0-未删除1-已删除)
*/
@TableField("is_deleted")
@TableLogic
private Integer isDeleted;
}

View File

@@ -0,0 +1,264 @@
package com.openhis.web.consultation.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 会诊申请单实体类
*
* @author system
* @date 2026-01-29
*/
@Data
@TableName("consultation_request")
public class ConsultationRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 会诊申请单号
*/
@TableField("consultation_id")
private String consultationId;
/**
* 患者ID
*/
@TableField("patient_id")
private Long patientId;
/**
* 就诊ID
*/
@TableField("encounter_id")
private Long encounterId;
/**
* 订单ID关联门诊医嘱表 wor_service_request.id
*/
@TableField("order_id")
private Long orderId;
/**
* 患者姓名
*/
@TableField("patient_name")
private String patientName;
/**
* 患者病历号
*/
@TableField("patient_bus_no")
private String patientBusNo;
/**
* 患者就诊卡号
*/
@TableField("patient_identifier_no")
private String patientIdentifierNo;
/**
* 性别
*/
@TableField("gender_enum")
private Integer genderEnum;
/**
* 年龄
*/
@TableField("age")
private Integer age;
/**
* 申请科室名称
*/
@TableField("department")
private String department;
/**
* 申请科室ID
*/
@TableField("department_id")
private Long departmentId;
/**
* 申请医生姓名
*/
@TableField("requesting_physician")
private String requestingPhysician;
/**
* 申请医生ID
*/
@TableField("requesting_physician_id")
private Long requestingPhysicianId;
/**
* 申请时间
*/
@TableField("consultation_request_date")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date consultationRequestDate;
/**
* 邀请对象显示文本
*/
@TableField("invited_object")
private String invitedObject;
/**
* 会诊时间
*/
@TableField("consultation_date")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date consultationDate;
/**
* 会诊目的
*/
@TableField("consultation_purpose")
private String consultationPurpose;
/**
* 门诊诊断
*/
@TableField("provisional_diagnosis")
private String provisionalDiagnosis;
/**
* 会诊项目ID关联wor_activity_definition表
*/
@TableField("consultation_activity_id")
private Long consultationActivityId;
/**
* 会诊项目名称(如:院内会诊、远程会诊等)
*/
@TableField("consultation_activity_name")
private String consultationActivityName;
/**
* 会诊意见
*/
@TableField("consultation_opinion")
private String consultationOpinion;
/**
* 会诊状态0-新开10-已提交20-已确认30-已签名40-已完成50-已取消)
*/
@TableField("consultation_status")
private Integer consultationStatus;
/**
* 紧急程度(一般/紧急)
*/
@TableField("consultation_urgency")
private String consultationUrgency;
/**
* 确认医生
*/
@TableField("confirming_physician")
private String confirmingPhysician;
/**
* 确认医生ID
*/
@TableField("confirming_physician_id")
private Long confirmingPhysicianId;
/**
* 确认时间
*/
@TableField("confirming_date")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date confirmingDate;
/**
* 签名
*/
@TableField("signature")
private String signature;
/**
* 签名医生ID
*/
@TableField("signature_physician_id")
private Long signaturePhysicianId;
/**
* 签名时间
*/
@TableField("signature_date")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date signatureDate;
/**
* 取消时间
*/
@TableField("cancel_nature_date")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date cancelNatureDate;
/**
* 作废原因
*/
@TableField("cancel_reason")
private String cancelReason;
/**
* 创建时间
*/
@TableField(value = "create_time", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新时间
*/
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/**
* 创建人
*/
@TableField(value = "create_by", fill = FieldFill.INSERT)
private String createBy;
/**
* 更新人
*/
@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/**
* 租户ID
*/
@TableField("tenant_id")
private Long tenantId;
/**
* 删除标识0-未删除1-已删除)
*/
@TableField("is_deleted")
@TableLogic
private Integer isDeleted;
/**
* 备注
*/
@TableField("remark")
private String remark;
}

View File

@@ -0,0 +1,68 @@
package com.openhis.web.consultation.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 会诊项目DTO
*
* @author system
* @date 2026-02-02
*/
@Data
public class ConsultationActivityDto implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 项目ID
* 使用 @JsonSerialize 注解将 Long 类型转换为 String避免前端 JavaScript 精度丢失
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/**
* 项目名称
*/
private String name;
/**
* 项目编号
*/
private String busNo;
/**
* 价格
*/
private BigDecimal price;
/**
* 类型枚举
*/
private Integer typeEnum;
/**
* 分类编码
*/
private String categoryCode;
/**
* 单位编码
*/
private String unitCode;
/**
* 单位名称
*/
private String unitCodeText;
/**
* 状态
*/
private Integer statusEnum;
}

View File

@@ -0,0 +1,190 @@
package com.openhis.web.consultation.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 会诊申请DTO
*
* @author system
* @date 2026-01-29
*/
@Data
public class ConsultationRequestDto implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/**
* 会诊申请单号
*/
private String consultationId;
/**
* 患者ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long patientId;
/**
* 患者姓名
*/
private String patientName;
/**
* 患者性别
*/
private Integer genderEnum;
/**
* 患者年龄
*/
private Integer age;
/**
* 病历号
*/
private String patientBusNo;
/**
* 就诊卡号
*/
private String patientIdentifierNo;
/**
* 就诊ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long encounterId;
/**
* 申请科室ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long departmentId;
/**
* 申请科室名称
*/
private String department;
/**
* 申请医生ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long requestingPhysicianId;
/**
* 申请医生姓名
*/
private String requestingPhysician;
/**
* 邀请对象显示文本
*/
private String invitedObject;
/**
* 邀请对象列表
*/
private List<InvitedObjectDto> invitedList;
/**
* 会诊时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date consultationDate;
/**
* 申请时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date consultationRequestDate;
/**
* 会诊目的
*/
private String consultationPurpose;
/**
* 门诊诊断
*/
private String provisionalDiagnosis;
/**
* 会诊项目ID关联wor_activity_definition表
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long consultationActivityId;
/**
* 会诊项目名称(如:院内会诊、远程会诊等)
*/
private String consultationActivityName;
/**
* 紧急程度1=普通2=紧急)
*/
private String consultationUrgency;
/**
* 会诊状态
*/
private Integer consultationStatus;
/**
* 会诊意见
*/
private String consultationOpinion;
/**
* 参与会诊医师
*/
private String consultingPhysicians;
/**
* 签名医生ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long signingPhysicianId;
/**
* 签名医生姓名
*/
private String signingPhysicianName;
/**
* 签名时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date signingTime;
/**
* 是否提交(保存时使用)
*/
private Boolean submitFlag;
/**
* 我的邀请状态查询我的邀请时使用0-待确认1-已确认2-已拒绝)
*/
private Integer myInvitedStatus;
/**
* 我的确认时间(查询我的邀请时使用)
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date myConfirmTime;
}

View File

@@ -0,0 +1,38 @@
package com.openhis.web.consultation.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 科室医生树DTO
*
* @author system
* @date 2026-01-29
*/
@Data
public class DepartmentTreeDto implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 科室ID序列化为字符串避免JavaScript精度丢失
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/**
* 科室名称
*/
private String label;
/**
* 子节点(医生列表)
*/
private List<PhysicianNodeDto> children;
}

View File

@@ -0,0 +1,43 @@
package com.openhis.web.consultation.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
/**
* 邀请对象DTO
*
* @author system
* @date 2026-01-29
*/
@Data
public class InvitedObjectDto implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 科室ID序列化为字符串避免JavaScript精度丢失
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long deptId;
/**
* 科室名称
*/
private String deptName;
/**
* 医生ID为空表示邀请整个科室序列化为字符串避免JavaScript精度丢失
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long physicianId;
/**
* 医生姓名
*/
private String physicianName;
}

View File

@@ -0,0 +1,32 @@
package com.openhis.web.consultation.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
/**
* 医生节点DTO
*
* @author system
* @date 2026-01-29
*/
@Data
public class PhysicianNodeDto implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 医生ID序列化为字符串避免JavaScript精度丢失
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/**
* 医生姓名
*/
private String label;
}

View File

@@ -0,0 +1,85 @@
package com.openhis.web.consultation.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 会诊状态枚举
*
* @author system
* @date 2026-02-05
*/
@Getter
@AllArgsConstructor
public enum ConsultationStatusEnum {
/**
* 新开(草稿)
*/
NEW(0, "新开"),
/**
* 已提交
*/
SUBMITTED(10, "已提交"),
/**
* 已确认
*/
CONFIRMED(20, "已确认"),
/**
* 已签名
*/
SIGNED(30, "已签名"),
/**
* 已完成
*/
COMPLETED(40, "已完成"),
/**
* 已取消
*/
CANCELLED(50, "已取消");
/**
* 状态码
*/
private final Integer code;
/**
* 状态描述
*/
private final String description;
/**
* 根据状态码获取枚举
*/
public static ConsultationStatusEnum getByCode(Integer code) {
if (code == null) {
return null;
}
for (ConsultationStatusEnum status : values()) {
if (status.getCode().equals(code)) {
return status;
}
}
return null;
}
/**
* 判断是否可以编辑
*/
public boolean canEdit() {
return this == NEW || this == SUBMITTED;
}
/**
* 判断是否可以取消
*/
public boolean canCancel() {
return this == NEW || this == SUBMITTED || this == CONFIRMED;
}
}

View File

@@ -0,0 +1,51 @@
package com.openhis.web.consultation.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 会诊紧急程度枚举
*
* @author system
* @date 2026-02-05
*/
@Getter
@AllArgsConstructor
public enum ConsultationUrgencyEnum {
/**
* 普通
*/
NORMAL("1", "普通"),
/**
* 紧急
*/
URGENT("2", "紧急");
/**
* 类型码
*/
private final String code;
/**
* 类型描述
*/
private final String description;
/**
* 根据类型码获取枚举
*/
public static ConsultationUrgencyEnum getByCode(String code) {
if (code == null) {
return null;
}
for (ConsultationUrgencyEnum urgency : values()) {
if (urgency.getCode().equals(code)) {
return urgency;
}
}
return null;
}
}

View File

@@ -0,0 +1,56 @@
package com.openhis.web.consultation.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 邀请状态枚举
*
* @author system
* @date 2026-02-05
*/
@Getter
@AllArgsConstructor
public enum InvitedStatusEnum {
/**
* 待确认
*/
PENDING(0, "待确认"),
/**
* 已确认
*/
CONFIRMED(1, "已确认"),
/**
* 已拒绝
*/
REJECTED(2, "已拒绝");
/**
* 状态码
*/
private final Integer code;
/**
* 状态描述
*/
private final String description;
/**
* 根据状态码获取枚举
*/
public static InvitedStatusEnum getByCode(Integer code) {
if (code == null) {
return null;
}
for (InvitedStatusEnum status : values()) {
if (status.getCode().equals(code)) {
return status;
}
}
return null;
}
}

View File

@@ -0,0 +1,17 @@
package com.openhis.web.consultation.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.openhis.web.consultation.domain.ConsultationInvited;
import org.apache.ibatis.annotations.Mapper;
/**
* 会诊邀请对象Mapper接口
*
* @author system
* @date 2026-01-30
*/
@Mapper
public interface ConsultationInvitedMapper extends BaseMapper<ConsultationInvited> {
}

View File

@@ -0,0 +1,17 @@
package com.openhis.web.consultation.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.openhis.web.consultation.domain.ConsultationRequest;
import org.apache.ibatis.annotations.Mapper;
/**
* 会诊申请Mapper接口
*
* @author system
* @date 2026-01-29
*/
@Mapper
public interface ConsultationRequestMapper extends BaseMapper<ConsultationRequest> {
}

View File

@@ -864,7 +864,17 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
serviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
serviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
serviceRequest.setCategoryEnum(adviceSaveDto.getCategoryEnum()); // 请求类型
// 🎯 判断是否为会诊医嘱如果categoryEnum为31则为会诊类型
Integer categoryEnum = adviceSaveDto.getCategoryEnum();
if (categoryEnum != null && categoryEnum == 31) {
// 会诊医嘱category_enum设置为31
serviceRequest.setCategoryEnum(31);
log.info("保存会诊医嘱category_enum=31");
} else {
// 普通诊疗医嘱
serviceRequest.setCategoryEnum(adviceSaveDto.getCategoryEnum());
}
serviceRequest.setActivityId(adviceSaveDto.getAdviceDefinitionId());// 诊疗定义id
serviceRequest.setPatientId(adviceSaveDto.getPatientId()); // 患者
serviceRequest.setRequesterId(adviceSaveDto.getPractitionerId()); // 开方医生

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.openhis.web.consultation.mapper.ConsultationRequestMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.openhis.web.consultation.domain.ConsultationRequest">
<id column="id" property="id" />
<result column="consultation_id" property="consultationId" />
<result column="patient_id" property="patientId" />
<result column="encounter_id" property="encounterId" />
<result column="order_id" property="orderId" />
<result column="patient_name" property="patientName" />
<result column="patient_bus_no" property="patientBusNo" />
<result column="patient_identifier_no" property="patientIdentifierNo" />
<result column="gender_enum" property="genderEnum" />
<result column="age" property="age" />
<result column="department" property="department" />
<result column="department_id" property="departmentId" />
<result column="requesting_physician" property="requestingPhysician" />
<result column="requesting_physician_id" property="requestingPhysicianId" />
<result column="consultation_request_date" property="consultationRequestDate" />
<result column="invited_object" property="invitedObject" />
<result column="invited_department_id" property="invitedDepartmentId" />
<result column="invited_physician_id" property="invitedPhysicianId" />
<result column="consultation_date" property="consultationDate" />
<result column="consultation_purpose" property="consultationPurpose" />
<result column="provisional_diagnosis" property="provisionalDiagnosis" />
<result column="consultation_opinion" property="consultationOpinion" />
<result column="consultation_status" property="consultationStatus" />
<result column="consultation_urgency" property="consultationUrgency" />
<result column="confirming_physician" property="confirmingPhysician" />
<result column="confirming_physician_id" property="confirmingPhysicianId" />
<result column="confirming_date" property="confirmingDate" />
<result column="signature" property="signature" />
<result column="signature_physician_id" property="signaturePhysicianId" />
<result column="signature_date" property="signatureDate" />
<result column="cancel_nature_date" property="cancelNatureDate" />
<result column="cancel_reason" property="cancelReason" />
<result column="create_time" property="createTime" />
<result column="update_time" property="updateTime" />
<result column="create_by" property="createBy" />
<result column="update_by" property="updateBy" />
<result column="tenant_id" property="tenantId" />
<result column="is_deleted" property="isDeleted" />
<result column="remark" property="remark" />
</resultMap>
</mapper>

View File

@@ -465,7 +465,8 @@
T2.part_percent AS part_percent,
ccd.name AS condition_definition_name,
T1.sort_number AS sort_number,
T1.based_on_id AS based_on_id
T1.based_on_id AS based_on_id,
T1.category_enum AS category_enum
FROM med_medication_request AS T1
LEFT JOIN med_medication_definition AS T2 ON T2.ID = T1.medication_id
AND T2.delete_flag = '0'
@@ -516,7 +517,8 @@
T2.part_percent AS part_percent,
'' AS condition_definition_name,
99 AS sort_number,
T1.based_on_id AS based_on_id
T1.based_on_id AS based_on_id,
T1.category_enum AS category_enum
FROM wor_device_request AS T1
LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id
AND T2.delete_flag = '0'
@@ -564,7 +566,8 @@
1 AS part_percent,
'' AS condition_definition_name,
99 AS sort_number,
T1.based_on_id AS based_on_id
T1.based_on_id AS based_on_id,
T1.category_enum AS category_enum
FROM wor_service_request AS T1
LEFT JOIN wor_activity_definition AS T2
ON T2.ID = T1.activity_id