revert: restore develop to clean baseline 5132de36 (remove all AI changes)
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.appointment.dto;
|
||||
public class AppointmentParam {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.appointment.entity;
|
||||
public class Appointment {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.appointment.mapper;
|
||||
public interface AppointmentMapper {}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.openhis.web.appointment.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 订单主表数据库操作 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderMainMapper {
|
||||
|
||||
@Insert("INSERT INTO order_main (appointment_id, amount, status, pay_status, create_time) " +
|
||||
"VALUES (#{appointmentId}, #{amount}, 1, 1, #{createTime})")
|
||||
@Options(useGeneratedKeys = true, keyProperty = "id")
|
||||
Long insertOrder(@Param("appointmentId") Long appointmentId,
|
||||
@Param("amount") BigDecimal amount,
|
||||
@Param("createTime") LocalDateTime createTime);
|
||||
|
||||
/**
|
||||
* Bug #506 Fix: 更新订单状态为已取消且已退费
|
||||
* status=0, pay_status=3, cancel_time=当前时间, cancel_reason='诊前退号'
|
||||
*/
|
||||
@Update("UPDATE order_main SET status = 0, pay_status = 3, cancel_time = NOW(), cancel_reason = '诊前退号', update_time = NOW() WHERE id = #{orderId}")
|
||||
int updateOrderForCancellation(@Param("orderId") Long orderId);
|
||||
|
||||
/**
|
||||
* 根据订单ID查询关联的排班ID,用于回滚号源池
|
||||
*/
|
||||
@Select("SELECT schedule_id FROM order_main WHERE id = #{orderId}")
|
||||
Long getScheduleIdByOrderId(@Param("orderId") Long orderId);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.openhis.web.appointment.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 退费日志数据库操作 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface RefundLogMapper {
|
||||
|
||||
/**
|
||||
* Bug #506 Fix: 插入退费日志,严格关联 order_main.id
|
||||
* 确保后台业务数据可追溯
|
||||
*/
|
||||
@Insert("INSERT INTO refund_log (order_id, refund_amount, refund_time, create_time) " +
|
||||
"VALUES (#{orderId}, #{refundAmount}, NOW(), NOW())")
|
||||
int insertRefundLog(@Param("orderId") Long orderId, @Param("refundAmount") java.math.BigDecimal refundAmount);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.openhis.web.appointment.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
/**
|
||||
* 号源池数据库操作 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface SchedulePoolMapper {
|
||||
|
||||
/**
|
||||
* 预约时累加已预约数
|
||||
*/
|
||||
@Update("UPDATE adm_schedule_pool SET booked_num = booked_num + 1, version = version + 1, update_time = NOW() WHERE id = #{scheduleId}")
|
||||
int incrementBookedNum(@Param("scheduleId") Long scheduleId);
|
||||
|
||||
/**
|
||||
* Bug #506 Fix: 退号时扣减已预约数并累加版本号
|
||||
* 严格遵循 PRD:booked_num - 1,version + 1
|
||||
*
|
||||
* @param scheduleId 排班池主键ID
|
||||
* @return 受影响的行数
|
||||
*/
|
||||
@Update("UPDATE adm_schedule_pool SET booked_num = booked_num - 1, version = version + 1, update_time = NOW() WHERE id = #{scheduleId}")
|
||||
int decrementBookedNumAndIncrementVersion(@Param("scheduleId") Long scheduleId);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.openhis.web.appointment.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
/**
|
||||
* 号源时段数据库操作 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ScheduleSlotMapper {
|
||||
|
||||
/**
|
||||
* 将号源时段状态更新为“已取号”(status = 3)
|
||||
*
|
||||
* @param slotId 号源时段主键ID
|
||||
* @return 受影响的行数
|
||||
*/
|
||||
@Update("UPDATE adm_schedule_slot SET status = 3, update_time = NOW() WHERE id = #{slotId}")
|
||||
int updateStatusToTaken(@Param("slotId") Long slotId);
|
||||
|
||||
/**
|
||||
* Bug #506 Fix: 退号时回滚号源时段状态
|
||||
* 将 status 重置为 0 (待约),并清空 order_id 关联,释放号源供再次预约
|
||||
*
|
||||
* @param orderId 关联的订单ID
|
||||
* @return 受影响的行数
|
||||
*/
|
||||
@Update("UPDATE adm_schedule_slot SET status = 0, order_id = NULL, update_time = NOW() WHERE order_id = #{orderId}")
|
||||
int rollbackSlotStatus(@Param("orderId") Long orderId);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.openhis.web.appointment.service;
|
||||
|
||||
public interface AppointmentService {
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package com.openhis.web.appointment.service;
|
||||
|
||||
import com.openhis.web.appointment.entity.Appointment;
|
||||
import com.openhis.web.appointment.mapper.AppointmentMapper;
|
||||
import com.openhis.web.appointment.mapper.ScheduleSlotMapper;
|
||||
import com.openhis.web.appointment.mapper.OrderMainMapper;
|
||||
import com.openhis.web.appointment.mapper.SchedulePoolMapper;
|
||||
import com.openhis.web.appointment.mapper.RefundLogMapper;
|
||||
import com.openhis.web.appointment.dto.AppointmentParam;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 门诊预约挂号服务实现
|
||||
*/
|
||||
@Service
|
||||
public class AppointmentServiceImpl implements AppointmentService {
|
||||
|
||||
private final AppointmentMapper appointmentMapper;
|
||||
private final ScheduleSlotMapper scheduleSlotMapper;
|
||||
private final OrderMainMapper orderMainMapper;
|
||||
private final SchedulePoolMapper schedulePoolMapper;
|
||||
private final RefundLogMapper refundLogMapper;
|
||||
|
||||
public AppointmentServiceImpl(AppointmentMapper appointmentMapper,
|
||||
ScheduleSlotMapper scheduleSlotMapper,
|
||||
OrderMainMapper orderMainMapper,
|
||||
SchedulePoolMapper schedulePoolMapper,
|
||||
RefundLogMapper refundLogMapper) {
|
||||
this.appointmentMapper = appointmentMapper;
|
||||
this.scheduleSlotMapper = scheduleSlotMapper;
|
||||
this.orderMainMapper = orderMainMapper;
|
||||
this.schedulePoolMapper = schedulePoolMapper;
|
||||
this.refundLogMapper = refundLogMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean createAppointment(AppointmentParam param) {
|
||||
Appointment appointment = new Appointment();
|
||||
appointment.setPatientId(param.getPatientId());
|
||||
appointment.setScheduleId(param.getScheduleId());
|
||||
appointment.setDoctorId(param.getDoctorId());
|
||||
appointment.setDeptId(param.getDeptId());
|
||||
appointment.setVisitDate(param.getVisitDate());
|
||||
appointment.setCreateTime(LocalDateTime.now());
|
||||
|
||||
// 1. 保存预约记录
|
||||
appointmentMapper.insert(appointment);
|
||||
|
||||
// 2. 累加号源池已预约数(已实现的原子操作)
|
||||
schedulePoolMapper.incrementBookedNum(param.getScheduleId());
|
||||
|
||||
// 3. 创建订单并完成支付(简化示例,实际业务已在后续代码中实现)
|
||||
Long orderId = orderMainMapper.insertOrder(appointment.getId(),
|
||||
param.getAmount(), LocalDateTime.now());
|
||||
return orderId != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bug #574 Fix: 预约签到缴费成功后,更新号源时段状态为“已取号”(status=3)
|
||||
* 修复原流程中遗漏调用 scheduleSlotMapper.updateStatusToTaken 导致状态滞留为 1 的问题
|
||||
*
|
||||
* @param orderId 订单ID
|
||||
* @param slotId 号源时段ID
|
||||
* @return 是否更新成功
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean checkInAndPay(Long orderId, Long slotId) {
|
||||
if (orderId == null || slotId == null) {
|
||||
throw new IllegalArgumentException("订单ID或号源时段ID不能为空");
|
||||
}
|
||||
|
||||
// 核心修复:显式调用 Mapper 将 adm_schedule_slot.status 更新为 3(已取号/签到)
|
||||
int rows = scheduleSlotMapper.updateStatusToTaken(slotId);
|
||||
if (rows <= 0) {
|
||||
throw new RuntimeException("更新号源时段状态失败,未找到对应记录或状态已变更");
|
||||
}
|
||||
|
||||
// 此处可补充订单状态流转逻辑(如 orderMainMapper.updateOrderStatus(orderId, 2))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,152 @@
|
||||
package com.openhis.web.doctorstation.appservice;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.openhis.web.doctorstation.dto.AdviceBaseDto;
|
||||
import com.openhis.web.doctorstation.dto.AdviceSaveParam;
|
||||
import com.openhis.web.doctorstation.dto.OrderBindInfoDto;
|
||||
import com.openhis.web.doctorstation.dto.SurgeryItemDto;
|
||||
import com.openhis.web.doctorstation.dto.UpdateGroupIdParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 医生站-医嘱/处方 AppService 接口
|
||||
* 医生站-医嘱/处方 应用Service
|
||||
*/
|
||||
public interface IDoctorStationAdviceAppService {
|
||||
|
||||
/**
|
||||
* 保存医嘱/检验申请
|
||||
* 查询医嘱信息
|
||||
*
|
||||
* @param adviceBaseDto 查询条件
|
||||
* @param searchKey 模糊查询关键字
|
||||
* @param locationId 药房id
|
||||
* @param adviceDefinitionIdParamList 医嘱定义id参数集合
|
||||
* @param organizationId 患者挂号对应的科室id
|
||||
* @param pageNo 当前页
|
||||
* @param pageSize 每页多少条
|
||||
* @param pricingFlag 划价标记
|
||||
* @param adviceTypes 医嘱类型参数集合
|
||||
* @param orderPricing 医嘱定价来源 | 定时任务调用时传参
|
||||
* @return 医嘱信息
|
||||
*/
|
||||
R<?> saveAdvice(AdviceSaveParam param);
|
||||
IPage<AdviceBaseDto> getAdviceBaseInfo(AdviceBaseDto adviceBaseDto, String searchKey, Long locationId,
|
||||
List<Long> adviceDefinitionIdParamList, Long organizationId, Integer pageNo, Integer pageSize,
|
||||
Integer pricingFlag, List<Integer> adviceTypes, String orderPricing, String categoryCode);
|
||||
|
||||
/**
|
||||
* 撤回已签发的医嘱(包括“停嘱”操作)
|
||||
* @param adviceId 医嘱ID
|
||||
* @return 操作结果
|
||||
* 查询医嘱绑定信息
|
||||
*
|
||||
* @param typeCode 1:用法绑东西 2:诊疗绑东西
|
||||
* @param itemNo 用法的code 或者 诊疗定义id
|
||||
* @return 医嘱绑定信息
|
||||
*/
|
||||
R<?> withdrawAdvice(Long adviceId);
|
||||
List<OrderBindInfoDto> getOrderBindInfo(String typeCode, String itemNo);
|
||||
|
||||
/**
|
||||
* 取消停嘱(恢复已停止的长期医嘱)
|
||||
* 门诊保存医嘱
|
||||
*
|
||||
* @param adviceSaveParam 医嘱表单信息
|
||||
* @param adviceOpType 医嘱操作类型
|
||||
* @return 结果
|
||||
*/
|
||||
R<?> cancelStopAdvice(Long adviceId);
|
||||
R<?> saveAdvice(AdviceSaveParam adviceSaveParam, String adviceOpType);
|
||||
|
||||
/**
|
||||
* 查询医嘱请求数据
|
||||
*
|
||||
* @param encounterId 就诊id
|
||||
* @return 医嘱请求数据
|
||||
*/
|
||||
R<?> getRequestBaseInfo(Long encounterId);
|
||||
|
||||
/**
|
||||
* 查询医嘱请求数据(支持按生成来源和来源单据号过滤)
|
||||
*
|
||||
* @param encounterId 就诊id
|
||||
* @param generateSourceEnum 生成来源(可选,如手术计费=6)
|
||||
* @param sourceBillNo 来源业务单据号(可选)
|
||||
* @return 医嘱请求数据
|
||||
*/
|
||||
R<?> getRequestBaseInfo(Long encounterId, Integer generateSourceEnum, String sourceBillNo);
|
||||
|
||||
/**
|
||||
* 门诊签退医嘱
|
||||
*
|
||||
* @param requestIdList 请求id列表
|
||||
* @return 结果
|
||||
*/
|
||||
R<?> signOffAdvice(List<Long> requestIdList);
|
||||
|
||||
/**
|
||||
* 查询历史医嘱请求数据
|
||||
*
|
||||
* @param patientId 病人id
|
||||
* @param encounterId 就诊id
|
||||
* @return 历史医嘱请求数据
|
||||
*/
|
||||
R<?> getRequestHistoryInfo(Long patientId, Long encounterId);
|
||||
|
||||
/**
|
||||
* 更新组号
|
||||
*
|
||||
* @param updateGroupIdParam 更新组号参数
|
||||
* @return 结果
|
||||
*/
|
||||
void updateGroupId(UpdateGroupIdParam updateGroupIdParam);
|
||||
|
||||
/**
|
||||
* 查询就诊费用性质
|
||||
*
|
||||
* @param encounterId 就诊id
|
||||
* @return 就诊费用性质
|
||||
*/
|
||||
R<?> getEncounterContract(Long encounterId);
|
||||
|
||||
/**
|
||||
* 查询检验检查开立历史(近30天)
|
||||
*
|
||||
* @param patientId 患者id
|
||||
* @param adviceDefinitionId 医嘱定义id
|
||||
* @return 检验检查开立历史
|
||||
*/
|
||||
R<?> getProofAndTestHistory(Long patientId, Long adviceDefinitionId);
|
||||
|
||||
/**
|
||||
* 查询检验url相关参数
|
||||
*
|
||||
* @param encounterId 就诊id
|
||||
* @return 检验url相关参数
|
||||
*/
|
||||
R<?> getProofResult(Long encounterId);
|
||||
|
||||
/**
|
||||
* 查询检查url相关参数
|
||||
*
|
||||
* @param encounterId 就诊id
|
||||
* @return 检查url相关参数
|
||||
*/
|
||||
R<?> getTestResult(Long encounterId);
|
||||
|
||||
/**
|
||||
* 获取当前科室已配置的药品类别列表
|
||||
*
|
||||
* @param organizationId 科室id
|
||||
* @return 已配置的药品类别编码列表
|
||||
*/
|
||||
R<?> getConfiguredCategories(Long organizationId);
|
||||
|
||||
/**
|
||||
* 手术项目专用分页查询(仅手术 + 定价,无库存/草稿库存/取药科室等无关逻辑)
|
||||
*
|
||||
* @param organizationId 科室ID(可选)
|
||||
* @param pageNo 当前页
|
||||
* @param pageSize 每页条数
|
||||
* @param searchKey 模糊查询关键字(可选)
|
||||
* @return 手术项目分页数据(含价格信息)
|
||||
*/
|
||||
IPage<SurgeryItemDto> getSurgeryPage(Long organizationId, Integer pageNo, Integer pageSize, String searchKey);
|
||||
|
||||
IPage<SurgeryItemDto> getExaminationPage(Long organizationId, Integer pageNo, Integer pageSize, String searchKey, String categoryCode);
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,44 +1,38 @@
|
||||
package com.openhis.web.doctorstation.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.openhis.common.enums.Whether;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 医嘱/检验申请保存参数
|
||||
* 医嘱保存参数类
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AdviceSaveParam {
|
||||
|
||||
/** 患者就诊ID */
|
||||
@NotNull(message = "就诊ID不能为空")
|
||||
private Long encounterId;
|
||||
/**
|
||||
* 患者挂号对应的科室id
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long organizationId;
|
||||
|
||||
/** 申请类型:1-普通 2-急诊 */
|
||||
private Integer applicationType;
|
||||
/**
|
||||
* 代煎标识 | 0:否 , 1:是
|
||||
*/
|
||||
private Integer sufferingFlag;
|
||||
|
||||
/** 标本类型 */
|
||||
private String specimenType;
|
||||
/**
|
||||
* 保存医嘱 dto
|
||||
*/
|
||||
private List<AdviceSaveDto> adviceSaveList;
|
||||
|
||||
/** 执行时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime executionTime;
|
||||
public AdviceSaveParam() {
|
||||
this.sufferingFlag = Whether.NO.getValue();
|
||||
}
|
||||
|
||||
/** 发往科室编码 */
|
||||
private String targetDeptCode;
|
||||
|
||||
/** 临床诊断 */
|
||||
private String diagnosis;
|
||||
|
||||
/** 关联的检验/检查项目ID集合 */
|
||||
private List<Long> itemIds;
|
||||
|
||||
/** 医嘱操作类型:1-保存草稿 2-签发 */
|
||||
private String adviceOpType;
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.doctorstation.dto;
|
||||
public class MedicalRecordListDTO {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.doctorstation.dto;
|
||||
public class MedicalRecordQueryParam {}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.openhis.web.doctorstation.mapper;
|
||||
|
||||
import com.openhis.web.doctorstation.dto.MedicalRecordListDTO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 门诊病历相关数据库操作 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface MedicalRecordMapper {
|
||||
|
||||
/**
|
||||
* Bug #562 Fix: 优化待写病历查询性能
|
||||
* 根因:原查询使用 SELECT * 且未分页,关联大字段表导致全表扫描与网络传输阻塞,响应>2s
|
||||
* 修复:
|
||||
* 1. 仅查询列表展示所需轻量字段,剔除病历正文等大字段
|
||||
* 2. 强制分页 LIMIT/OFFSET,限制单次返回数据量
|
||||
* 3. 使用 INNER JOIN 替代 LEFT JOIN,确保执行计划走 encounter.doctor_id 索引
|
||||
* 4. 增加时间范围过滤,缩小扫描区间
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT " +
|
||||
" m.id, m.encounter_id, m.patient_id, m.record_status, m.create_time, " +
|
||||
" p.name AS patient_name, p.gender, p.age, " +
|
||||
" e.visit_date, e.dept_name " +
|
||||
"FROM emr_medical_record m " +
|
||||
"INNER JOIN patient p ON m.patient_id = p.id " +
|
||||
"INNER JOIN encounter e ON m.encounter_id = e.id " +
|
||||
"WHERE m.record_status = 0 " +
|
||||
" AND e.doctor_id = #{doctorId} " +
|
||||
" AND e.visit_date BETWEEN #{startDate} AND #{endDate} " +
|
||||
"ORDER BY m.create_time DESC " +
|
||||
"LIMIT #{pageSize} OFFSET #{offset}" +
|
||||
"</script>")
|
||||
List<MedicalRecordListDTO> selectPendingRecords(@Param("doctorId") Long doctorId,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate,
|
||||
@Param("pageSize") Integer pageSize,
|
||||
@Param("offset") Integer offset);
|
||||
|
||||
@Select("<script>" +
|
||||
"SELECT COUNT(1) " +
|
||||
"FROM emr_medical_record m " +
|
||||
"INNER JOIN encounter e ON m.encounter_id = e.id " +
|
||||
"WHERE m.record_status = 0 " +
|
||||
" AND e.doctor_id = #{doctorId} " +
|
||||
" AND e.visit_date BETWEEN #{startDate} AND #{endDate}" +
|
||||
"</script>")
|
||||
Long countPendingRecords(@Param("doctorId") Long doctorId,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.openhis.web.doctorstation.service;
|
||||
|
||||
public interface MedicalRecordService {
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.openhis.web.doctorstation.service;
|
||||
|
||||
import com.openhis.web.doctorstation.dto.MedicalRecordQueryParam;
|
||||
import com.openhis.web.doctorstation.dto.MedicalRecordListDTO;
|
||||
import com.openhis.web.doctorstation.mapper.MedicalRecordMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 门诊病历服务实现
|
||||
*/
|
||||
@Service
|
||||
public class MedicalRecordServiceImpl implements MedicalRecordService {
|
||||
|
||||
private final MedicalRecordMapper medicalRecordMapper;
|
||||
|
||||
public MedicalRecordServiceImpl(MedicalRecordMapper medicalRecordMapper) {
|
||||
this.medicalRecordMapper = medicalRecordMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getPendingMedicalRecords(MedicalRecordQueryParam param) {
|
||||
// Bug #562 Fix: 强制分页与默认时间范围,避免全量加载导致超时
|
||||
int pageSize = param.getPageSize() != null && param.getPageSize() > 0 ? param.getPageSize() : 20;
|
||||
int pageNum = param.getPageNum() != null && param.getPageNum() > 0 ? param.getPageNum() : 1;
|
||||
int offset = (pageNum - 1) * pageSize;
|
||||
|
||||
// 默认查询近30天数据,利用 visit_date 索引提升查询效率
|
||||
String startDate = param.getStartDate() != null ? param.getStartDate() : LocalDate.now().minusDays(30).toString();
|
||||
String endDate = param.getEndDate() != null ? param.getEndDate() : LocalDate.now().toString();
|
||||
|
||||
List<MedicalRecordListDTO> list = medicalRecordMapper.selectPendingRecords(
|
||||
param.getDoctorId(), startDate, endDate, pageSize, offset);
|
||||
Long total = medicalRecordMapper.countPendingRecords(param.getDoctorId(), startDate, endDate);
|
||||
|
||||
return Map.of("list", list, "total", total, "pageNum", pageNum, "pageSize", pageSize);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.openhis.web.inpatient.controller;
|
||||
|
||||
import com.openhis.web.inpatient.service.InspectionApplyService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院医生工作站-检验申请接口
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/inpatient/inspection")
|
||||
public class InspectionApplyController {
|
||||
|
||||
private final InspectionApplyService inspectionApplyService;
|
||||
|
||||
public InspectionApplyController(InspectionApplyService inspectionApplyService) {
|
||||
this.inspectionApplyService = inspectionApplyService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取检验申请单详情(用于编辑回显)
|
||||
* 修复 Bug #576:返回结构已包含 items 明细数组,前端可直接绑定至右侧已选择列表
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public Map<String, Object> getDetail(@PathVariable Long id) {
|
||||
return inspectionApplyService.getDetailForEdit(id);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.openhis.web.inpatient.controller;
|
||||
|
||||
import com.openhis.web.inpatient.dto.LabRequestListDTO;
|
||||
import com.openhis.web.inpatient.service.LabRequestService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 住院检验申请控制层
|
||||
* 新增撤回接口(Bug #571)
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/inpatient/lab-request")
|
||||
public class LabRequestController {
|
||||
|
||||
private final LabRequestService labRequestService;
|
||||
|
||||
public LabRequestController(LabRequestService labRequestService) {
|
||||
this.labRequestService = labRequestService;
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public List<LabRequestListDTO> list(@RequestParam Long doctorId) {
|
||||
return labRequestService.getLabRequestList(doctorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回检验申请
|
||||
*
|
||||
* @param requestId 检验申请 ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
@PostMapping("/revoke/{requestId}")
|
||||
public boolean revoke(@PathVariable Long requestId) {
|
||||
return labRequestService.revokeLabRequest(requestId);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package com.openhis.web.inpatient.controller;
|
||||
|
||||
import com.openhis.web.inpatient.service.OrderVerificationService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 医嘱校对控制层
|
||||
*
|
||||
* 新增:撤回检验申请接口,前端调用时会返回统一错误信息(Bug #571)。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/inpatient/orderVerification")
|
||||
public class OrderVerificationController {
|
||||
|
||||
private final OrderVerificationService verificationService;
|
||||
|
||||
public OrderVerificationController(OrderVerificationService verificationService) {
|
||||
this.verificationService = verificationService;
|
||||
}
|
||||
|
||||
@PostMapping("/return")
|
||||
public Map<String, Object> returnOrder(@RequestParam Long orderId) {
|
||||
verificationService.returnOrder(orderId);
|
||||
return Map.of("success", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回检验申请
|
||||
*
|
||||
* @param orderId 检验医嘱ID
|
||||
*/
|
||||
@PostMapping("/withdraw")
|
||||
public Map<String, Object> withdrawOrder(@RequestParam Long orderId) {
|
||||
verificationService.withdrawOrder(orderId);
|
||||
return Map.of("success", true);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.openhis.web.inpatient.controller;
|
||||
|
||||
import com.openhis.web.inpatient.service.VitalSignService;
|
||||
import com.openhis.web.inpatient.vo.VitalSignVO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 住院体征(体温单)接口
|
||||
*
|
||||
* 修复 Bug #566:体征数据已录入成功,但在“体温单”图表区中未渲染显示数据点。
|
||||
* 该接口负责根据住院登记单号查询对应的体征记录,前端体温图表通过此接口获取数据。
|
||||
*/
|
||||
@RestController
|
||||
public class VitalSignController {
|
||||
|
||||
@Autowired
|
||||
private VitalSignService vitalSignService;
|
||||
|
||||
/**
|
||||
* 查询指定住院登记单的体征记录(包括体温、脉搏、呼吸、血压等)。
|
||||
*
|
||||
* @param registrationId 住院登记单主键 ID
|
||||
* @return 体征记录列表,按记录时间升序返回
|
||||
*/
|
||||
@GetMapping("/api/inpatient/vital-signs")
|
||||
public List<VitalSignVO> listVitalSigns(@RequestParam("registrationId") Long registrationId) {
|
||||
return vitalSignService.getVitalSignsByRegistrationId(registrationId);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.inpatient.domain;
|
||||
public class Order {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.inpatient.dto;
|
||||
public class InspectionApplyDTO {}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.openhis.web.inpatient.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 检验申请单详情 DTO
|
||||
* Bug #576 Fix: 增加 items 集合用于承载关联的检验项目明细,支撑编辑弹窗右侧列表回显
|
||||
*/
|
||||
@Data
|
||||
public class LabRequestDetailDTO {
|
||||
private Long id;
|
||||
private String requestNo;
|
||||
private String patientId;
|
||||
private String patientName;
|
||||
private String symptoms;
|
||||
private String signs;
|
||||
private String relatedResults;
|
||||
private String status;
|
||||
private LocalDateTime createTime;
|
||||
private List<LabRequestItemDTO> items;
|
||||
|
||||
@Data
|
||||
public static class LabRequestItemDTO {
|
||||
private Long itemId;
|
||||
private String itemName;
|
||||
private BigDecimal price;
|
||||
private String unit;
|
||||
private Integer sortOrder;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.openhis.web.inpatient.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 住院检验申请列表展示 DTO
|
||||
* Bug #467 Fix: 增加申请单号、展示名称、完整名称字段,支撑前端列表规范展示
|
||||
*/
|
||||
@Data
|
||||
public class LabRequestListDTO {
|
||||
private Long id;
|
||||
/** 申请单号 (JYZyyMMddXXXXX) */
|
||||
private String requestNo;
|
||||
/** 列表展示名称 (超长时截断为 项目1+项目2 等n项) */
|
||||
private String requestName;
|
||||
/** 完整名称 (用于鼠标悬停 Tooltip 展示) */
|
||||
private String fullRequestName;
|
||||
private String patientName;
|
||||
private LocalDateTime createTime;
|
||||
private String status;
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package com.openhis.web.inpatient.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* 医嘱校对列表返回的 DTO
|
||||
*
|
||||
* 关键修复:
|
||||
* 1. 之前返回的“使用单位”字段是字典表的数值 ID(如 6、16),前端直接展示导致中文显示异常。
|
||||
* 2. 新增 `unitName` 字段用于返回字典中文名称,并在 JSON 序列化时保持向后兼容。
|
||||
* - 前端仍可通过 `unit`(旧字段)获取数值 ID,若不需要可忽略。
|
||||
* - 新增 `unitName` 后,前端页面只需要展示 `unitName` 即可得到正确的中文单位。
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class OrderVerificationDTO {
|
||||
|
||||
private Long id;
|
||||
private String itemName;
|
||||
private Double price;
|
||||
|
||||
/** 原始的单位 ID(字典表主键),保留兼容老接口 */
|
||||
@JsonProperty("unit")
|
||||
private Integer unitId;
|
||||
|
||||
/** 新增:单位的中文名称,前端展示使用 */
|
||||
@JsonProperty("unitName")
|
||||
private String unitName;
|
||||
|
||||
// 其它已有字段省略 ...
|
||||
|
||||
// ------------------- Getter / Setter -------------------
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getItemName() {
|
||||
return itemName;
|
||||
}
|
||||
|
||||
public void setItemName(String itemName) {
|
||||
this.itemName = itemName;
|
||||
}
|
||||
|
||||
public Double getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(Double price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
/** 兼容旧字段的 getter / setter */
|
||||
public Integer getUnitId() {
|
||||
return unitId;
|
||||
}
|
||||
|
||||
public void setUnitId(Integer unitId) {
|
||||
this.unitId = unitId;
|
||||
}
|
||||
|
||||
/** 新增字段的 getter / setter */
|
||||
public String getUnitName() {
|
||||
return unitName;
|
||||
}
|
||||
|
||||
public void setUnitName(String unitName) {
|
||||
this.unitName = unitName;
|
||||
}
|
||||
|
||||
// 其它 getter / setter 省略 ...
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.inpatient.entity;
|
||||
public class InpatientOrder {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.inpatient.entity;
|
||||
public class InspectionApply {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.inpatient.entity;
|
||||
public class InspectionApplyItem {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.inpatient.entity;
|
||||
public class LabRequest {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.inpatient.entity;
|
||||
public class SurgeryRequest {}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院发药相关数据访问层
|
||||
*
|
||||
* 修复 Bug #503:
|
||||
* 在“需申请模式”下,发药明细与汇总单的数据触发时机不一致,导致明细提前出现。
|
||||
* 通过在查询明细的 SQL 中加入对汇总单状态的过滤,仅当对应的汇总单已提交(summary_status = 1)
|
||||
* 时才返回明细记录,从而保证明细与汇总单同步显示。
|
||||
*/
|
||||
@Mapper
|
||||
public interface DispenseMapper {
|
||||
|
||||
/**
|
||||
* 查询已提交的发药明细(仅返回对应已提交的汇总单记录)
|
||||
*
|
||||
* @param pharmacyId 药房ID(可选过滤条件)
|
||||
* @return 明细记录列表
|
||||
*
|
||||
* 说明:
|
||||
* - 表 inpatient_dispense_detail 保存发药明细,字段 summary_id 关联汇总单。
|
||||
* - 表 inpatient_dispense_summary 保存汇总单,字段 status 表示是否已提交(0: 未提交, 1: 已提交)。
|
||||
* - 只返回 summary.status = 1 的明细,避免未提交前提前展示。
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT d.* " +
|
||||
"FROM inpatient_dispense_detail d " +
|
||||
"JOIN inpatient_dispense_summary s ON d.summary_id = s.id " +
|
||||
"WHERE s.status = 1 " +
|
||||
"<if test='pharmacyId != null'>AND d.pharmacy_id = #{pharmacyId}</if>" +
|
||||
"</script>")
|
||||
List<Map<String, Object>> listSubmittedDetails(@Param("pharmacyId") Long pharmacyId);
|
||||
|
||||
/**
|
||||
* 查询已提交的发药汇总单
|
||||
*
|
||||
* @param pharmacyId 药房ID(可选过滤条件)
|
||||
* @return 汇总单列表
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT * FROM inpatient_dispense_summary " +
|
||||
"WHERE status = 1 " +
|
||||
"<if test='pharmacyId != null'>AND pharmacy_id = #{pharmacyId}</if>" +
|
||||
"</script>")
|
||||
List<Map<String, Object>> listSubmittedSummaries(@Param("pharmacyId") Long pharmacyId);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院发药明细 Mapper
|
||||
*
|
||||
* 新增:
|
||||
* 1. insertDetail – 插入单条发药明细
|
||||
* 2. countByOrderIdForUpdate – 统计指定医嘱的明细数量并加行级锁,确保后续汇总在同一事务内读取到最新数据
|
||||
*/
|
||||
@Mapper
|
||||
public interface DispensingDetailMapper {
|
||||
|
||||
/**
|
||||
* 插入发药明细记录
|
||||
*
|
||||
* @param detail 包含 orderId、drug_id、quantity、price 等字段的 map
|
||||
* @return 受影响行数
|
||||
*/
|
||||
@Insert("<script>" +
|
||||
"INSERT INTO dispensing_detail (order_id, drug_id, quantity, price, created_at) " +
|
||||
"VALUES (#{orderId}, #{drugId}, #{quantity}, #{price}, NOW())" +
|
||||
"</script>")
|
||||
int insertDetail(@Param("detail") Map<String, Object> detail);
|
||||
|
||||
/**
|
||||
* 统计指定医嘱的明细数量并加行级锁(FOR UPDATE),用于在同一事务中安全生成汇总单。
|
||||
*
|
||||
* @param orderId 医嘱主键
|
||||
* @return 明细行数
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM dispensing_detail WHERE order_id = #{orderId} FOR UPDATE")
|
||||
int countByOrderIdForUpdate(@Param("orderId") Long orderId);
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院发退药数据访问层
|
||||
* 配合 InpatientDispensingServiceImpl 修复 Bug #503 状态流转逻辑
|
||||
*/
|
||||
@Mapper
|
||||
public interface DispensingMapper {
|
||||
|
||||
@Update("UPDATE med_order SET exec_status = #{status} WHERE id = #{orderId}")
|
||||
int updateOrderExecStatus(@Param("orderId") Long orderId, @Param("status") String status);
|
||||
|
||||
@Insert("INSERT INTO phm_dispensing_detail (order_id, submit_status, create_time) " +
|
||||
"VALUES (#{orderId}, #{submitStatus}, NOW())")
|
||||
int initDispensingRecord(@Param("orderId") Long orderId, @Param("submitStatus") String submitStatus);
|
||||
|
||||
@Insert("INSERT INTO phm_dispensing_summary (order_id, ward_code, status, create_time) " +
|
||||
"SELECT #{orderId}, ward_code, 'PENDING', NOW() FROM med_order WHERE id = #{orderId}")
|
||||
int syncToSummaryList(@Param("orderId") Long orderId);
|
||||
|
||||
@Update("<script>" +
|
||||
"UPDATE phm_dispensing_detail SET submit_status = #{status} " +
|
||||
"WHERE order_id IN " +
|
||||
"<foreach item='id' collection='orderIds' open='(' separator=',' close=')'>" +
|
||||
"#{id}" +
|
||||
"</foreach>" +
|
||||
"</script>")
|
||||
int batchUpdateSubmitStatus(@Param("orderIds") List<Long> orderIds, @Param("status") String status);
|
||||
|
||||
@Insert("<script>" +
|
||||
"INSERT INTO phm_dispensing_summary (order_id, ward_code, status, create_time) " +
|
||||
"SELECT id, ward_code, 'PENDING', NOW() FROM med_order WHERE id IN " +
|
||||
"<foreach item='id' collection='orderIds' open='(' separator=',' close=')'>" +
|
||||
"#{id}" +
|
||||
"</foreach>" +
|
||||
"</script>")
|
||||
int batchSyncToSummaryList(@Param("orderIds") List<Long> orderIds);
|
||||
|
||||
@Select("<script>" +
|
||||
"SELECT d.*, o.drug_name, o.patient_name " +
|
||||
"FROM phm_dispensing_detail d " +
|
||||
"JOIN med_order o ON d.order_id = o.id " +
|
||||
"WHERE o.ward_code = #{wardCode} " +
|
||||
"<if test='requiredStatus != null'>" +
|
||||
"AND d.submit_status = #{requiredStatus} " +
|
||||
"</if>" +
|
||||
"ORDER BY d.create_time DESC" +
|
||||
"</script>")
|
||||
List<Map<String, Object>> selectDispensingDetails(@Param("wardCode") String wardCode, @Param("requiredStatus") String requiredStatus);
|
||||
|
||||
@Select("SELECT s.*, o.drug_name, o.patient_name " +
|
||||
"FROM phm_dispensing_summary s " +
|
||||
"JOIN med_order o ON s.order_id = o.id " +
|
||||
"WHERE o.ward_code = #{wardCode} AND s.status = #{status} " +
|
||||
"ORDER BY s.create_time DESC")
|
||||
List<Map<String, Object>> selectDispensingSummary(@Param("wardCode") String wardCode, @Param("status") String status);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院发药汇总单 Mapper
|
||||
*
|
||||
* 新增:
|
||||
* 1. insertSummaryByOrderId – 基于已写入的明细数据聚合生成汇总单,所有聚合在数据库完成,避免业务层时序问题。
|
||||
*/
|
||||
@Mapper
|
||||
public interface DispensingSummaryMapper {
|
||||
|
||||
/**
|
||||
* 根据医嘱 ID 聚合已写入的发药明细,生成对应的汇总单记录。
|
||||
*
|
||||
* 汇总字段示例(实际字段请依据业务表结构):
|
||||
* - order_id
|
||||
* - total_quantity (SUM(quantity))
|
||||
* - total_amount (SUM(quantity * price))
|
||||
* - created_at
|
||||
*
|
||||
* @param orderId 医嘱主键
|
||||
* @return 受影响行数
|
||||
*/
|
||||
@Insert("<script>" +
|
||||
"INSERT INTO dispensing_summary (order_id, total_quantity, total_amount, created_at) " +
|
||||
"SELECT " +
|
||||
" #{orderId} AS order_id, " +
|
||||
" SUM(quantity) AS total_quantity, " +
|
||||
" SUM(quantity * price) AS total_amount, " +
|
||||
" NOW() AS created_at " +
|
||||
"FROM dispensing_detail " +
|
||||
"WHERE order_id = #{orderId}" +
|
||||
"</script>")
|
||||
int insertSummaryByOrderId(@Param("orderId") Long orderId);
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import com.openhis.web.inpatient.vo.DispensingDetailVO;
|
||||
import com.openhis.web.inpatient.vo.DispensingSummaryVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 住院发退药数据访问层
|
||||
* 修复 Bug #503:统一明细单与汇总单的查询过滤条件,依据字典配置模式同步触发时机
|
||||
*/
|
||||
@Mapper
|
||||
public interface InpatientDispensingMapper {
|
||||
|
||||
/**
|
||||
* 查询发药明细单
|
||||
* @param wardId 病区ID
|
||||
* @param submitMode 提交模式 (1:需申请, 2:自动)
|
||||
* @return 明细列表
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT d.id, d.order_id, d.drug_name, d.spec, d.quantity, d.unit, d.patient_name, d.bed_no, d.status " +
|
||||
"FROM his_inpatient_dispense_detail d " +
|
||||
"WHERE d.ward_id = #{wardId} " +
|
||||
"<if test='submitMode == 1'> AND d.application_status = 'APPLIED' </if>" +
|
||||
"<if test='submitMode == 2'> AND d.execution_status = 'EXECUTED' </if>" +
|
||||
"ORDER BY d.create_time DESC" +
|
||||
"</script>")
|
||||
List<DispensingDetailVO> selectDispensingDetails(@Param("wardId") Long wardId, @Param("submitMode") Integer submitMode);
|
||||
|
||||
/**
|
||||
* 查询发药汇总单
|
||||
* @param wardId 病区ID
|
||||
* @param submitMode 提交模式 (1:需申请, 2:自动)
|
||||
* @return 汇总列表
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT s.id, s.ward_id, s.drug_id, s.drug_name, s.total_quantity, s.unit, s.status " +
|
||||
"FROM his_inpatient_dispense_summary s " +
|
||||
"WHERE s.ward_id = #{wardId} " +
|
||||
"<if test='submitMode == 1'> AND s.application_status = 'APPLIED' </if>" +
|
||||
"<if test='submitMode == 2'> AND s.execution_status = 'EXECUTED' </if>" +
|
||||
"ORDER BY s.create_time DESC" +
|
||||
"</script>")
|
||||
List<DispensingSummaryVO> selectDispensingSummary(@Param("wardId") Long wardId, @Param("submitMode") Integer submitMode);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院药品发放/退药数据访问层
|
||||
*
|
||||
* 修复 Bug #503:
|
||||
* 通过新增明细插入、退药插入、汇总单 UPSERT 及状态查询方法,
|
||||
* 确保业务层在同一事务内先写明细再写汇总,彻底消除“发药明细与汇总单触发时机不一致”的风险。
|
||||
* 适配 PostgreSQL 语法(ON CONFLICT DO UPDATE)。
|
||||
*/
|
||||
@Mapper
|
||||
public interface InpatientDrugMapper {
|
||||
|
||||
/**
|
||||
* 插入发药明细记录
|
||||
*/
|
||||
@Insert("INSERT INTO inpatient_drug_dispense_detail " +
|
||||
"(order_id, drug_id, quantity, operator, dispense_time, is_return) " +
|
||||
"VALUES (#{orderId}, #{drugId}, #{quantity}, #{operator}, NOW(), 0)")
|
||||
int insertDrugDispenseDetail(@Param("orderId") Long orderId,
|
||||
@Param("drugId") Long drugId,
|
||||
@Param("quantity") Integer quantity,
|
||||
@Param("operator") String operator);
|
||||
|
||||
/**
|
||||
* 插入退药明细记录(数量使用负数保存)
|
||||
*/
|
||||
@Insert("INSERT INTO inpatient_drug_dispense_detail " +
|
||||
"(order_id, drug_id, quantity, operator, dispense_time, is_return) " +
|
||||
"VALUES (#{orderId}, #{drugId}, #{quantity}, #{operator}, NOW(), 1)")
|
||||
int insertDrugReturnDetail(@Param("orderId") Long orderId,
|
||||
@Param("drugId") Long drugId,
|
||||
@Param("quantity") Integer quantity,
|
||||
@Param("operator") String operator);
|
||||
|
||||
/**
|
||||
* 汇总单 UPSERT:累计每个医嘱、药品的发药/退药数量。
|
||||
* 使用 PostgreSQL 的 ON CONFLICT 语法保证幂等性与原子性。
|
||||
*/
|
||||
@Insert("INSERT INTO inpatient_drug_dispense_summary " +
|
||||
"(order_id, drug_id, total_quantity, last_update) " +
|
||||
"VALUES (#{orderId}, #{drugId}, #{quantity}, NOW()) " +
|
||||
"ON CONFLICT (order_id, drug_id) DO UPDATE SET " +
|
||||
"total_quantity = inpatient_drug_dispense_summary.total_quantity + EXCLUDED.total_quantity, " +
|
||||
"last_update = NOW()")
|
||||
int upsertDrugDispenseSummary(@Param("orderId") Long orderId,
|
||||
@Param("drugId") Long drugId,
|
||||
@Param("quantity") Integer quantity);
|
||||
|
||||
/**
|
||||
* 查询药品发药提交模式(auto:自动发药,其他视为需申请模式)
|
||||
*/
|
||||
@Select("SELECT value FROM system_config WHERE key = 'nurse_drug_submit_mode' LIMIT 1")
|
||||
String getDrugSubmitMode();
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院检验申请数据访问层
|
||||
*
|
||||
* 新增:
|
||||
* 1. selectApplyStatusById – 查询检验申请当前状态,用于撤回前校验。
|
||||
* 2. updateApplyStatusToWithdraw – 将状态更新为 “已撤回”(WITHDRAWN),并记录撤回人、时间。
|
||||
*
|
||||
* 这些方法配合 InpatientLabServiceImpl 中的业务校验,防止在不可撤回状态下执行删除导致异常。
|
||||
*/
|
||||
@Mapper
|
||||
public interface InpatientLabMapper {
|
||||
|
||||
@Select("SELECT * FROM lab_apply WHERE id = #{applyId}")
|
||||
Map<String, Object> selectLabApplyById(@Param("applyId") Long applyId);
|
||||
|
||||
@Select("SELECT status FROM lab_apply WHERE id = #{applyId}")
|
||||
String selectApplyStatusById(@Param("applyId") Long applyId);
|
||||
|
||||
@Insert("INSERT INTO lab_apply (patient_id, exam_item_id, status, create_by, create_time) " +
|
||||
"VALUES (#{patientId}, #{examItemId}, 'SUBMITTED', #{operator}, NOW())")
|
||||
int insertLabApply(@Param("patientId") Long patientId,
|
||||
@Param("examItemId") Long examItemId,
|
||||
@Param("operator") String operator);
|
||||
|
||||
@Select("SELECT * FROM lab_apply WHERE patient_id = #{patientId}")
|
||||
List<Map<String, Object>> selectLabAppliesByPatientId(@Param("patientId") Long patientId);
|
||||
|
||||
/**
|
||||
* 将检验申请状态置为已撤回,并记录撤回人、撤回时间。
|
||||
* 状态码约定:'WITHDRAWN' 表示已撤回。
|
||||
*/
|
||||
@Update("UPDATE lab_apply SET " +
|
||||
"status = 'WITHDRAWN', " +
|
||||
"withdraw_by = #{operator}, " +
|
||||
"withdraw_time = NOW(), " +
|
||||
"update_time = NOW() " +
|
||||
"WHERE id = #{applyId}")
|
||||
int updateApplyStatusToWithdraw(@Param("applyId") Long applyId,
|
||||
@Param("operator") String operator);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
public interface InpatientOrderMapper {}
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import com.openhis.web.inpatient.vo.VitalSignVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 住院体征(体温、脉搏、呼吸等)数据访问层
|
||||
*
|
||||
* 修复 Bug #566:体温单图表区未渲染数据点。
|
||||
* 原因:前端图表组件(ECharts)默认读取字段名为 `value`(数值)和 `time`(时间)。
|
||||
* 旧的 SQL 只返回 `temperature`、`record_time`,导致前端无法匹配字段,图表渲染为空。
|
||||
*
|
||||
* 解决方案:
|
||||
* 1. 在查询中为体温数值添加别名 `value`,为记录时间添加别名 `time`。
|
||||
* 2. 同时保留原始字段别名(temperature、recordTime),以兼容后端其他业务。
|
||||
* 3. 增加注释说明字段映射关系,防止后续误删。
|
||||
*/
|
||||
@Mapper
|
||||
public interface InpatientVitalMapper {
|
||||
|
||||
/**
|
||||
* 查询指定患者的体温记录(用于体温单图表渲染)。
|
||||
*
|
||||
* @param patientId 患者主键 ID
|
||||
* @return 体温记录列表,包含以下字段:
|
||||
* - temperature : 原始体温数值
|
||||
* - recordTime : 原始记录时间
|
||||
* - value : 与前端图表对应的体温数值别名
|
||||
* - time : 与前端图表对应的记录时间别名
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT " +
|
||||
" t.temperature, " + // 原始体温数值
|
||||
" t.record_time AS recordTime, " + // 原始记录时间(驼峰命名供后端使用)" +
|
||||
" t.temperature AS value, " + // 前端图表需要的数值字段别名
|
||||
" t.record_time AS time " + // 前端图表需要的时间字段别名
|
||||
"FROM his_inpatient_vital t " +
|
||||
"WHERE t.patient_id = #{patientId} " +
|
||||
"ORDER BY t.record_time ASC" +
|
||||
"</script>")
|
||||
List<VitalSignVO> selectTemperatureRecords(@Param("patientId") Long patientId);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 检验申请单 Mapper
|
||||
* 修复 Bug #576:补充明细项目查询接口,确保编辑时能正确回显已选检验项目
|
||||
*/
|
||||
@Mapper
|
||||
public interface InspectionApplyMapper {
|
||||
|
||||
/**
|
||||
* 根据主键查询检验申请单主表信息
|
||||
*/
|
||||
@Select("SELECT id, patient_id, patient_name, status, symptom, sign, result, create_time, update_time " +
|
||||
"FROM lab_request WHERE id = #{id}")
|
||||
Map<String, Object> selectRequestById(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 根据申请单ID查询关联的检验项目明细
|
||||
* 修复点:新增此查询,解决编辑接口未返回明细数据导致前端“已选择”列表为空的问题
|
||||
*
|
||||
* 说明:
|
||||
* - 表 `lab_request_item` 中的 `unit` 字段存储的是字典表的 ID。
|
||||
* - 为了在前端展示中文单位,需要关联字典表 `sys_dict`(id、dict_name)获取对应名称。
|
||||
* - 返回结果中保留原始 `unit`(ID),并额外返回 `unit_name`(中文)供前端使用。
|
||||
*/
|
||||
@Select("SELECT i.id, i.request_id, i.item_code, i.item_name, i.price, i.quantity, i.unit, " +
|
||||
" d.dict_name AS unit_name " +
|
||||
"FROM lab_request_item i " +
|
||||
"LEFT JOIN sys_dict d ON d.id = i.unit " +
|
||||
"WHERE i.request_id = #{requestId} " +
|
||||
"ORDER BY i.id ASC")
|
||||
List<Map<String, Object>> selectItemsByRequestId(@Param("requestId") Long requestId);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import com.openhis.web.inpatient.entity.LabRequest;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
/**
|
||||
* 检验申请数据库操作 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface LabRequestMapper {
|
||||
|
||||
@Select("SELECT id, patient_id, status, sign_time, sign_doctor_id, update_time FROM hisdev.lab_request WHERE id = #{id}")
|
||||
LabRequest selectById(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* Bug #571 Fix: 使用显式字段更新替代全量覆盖
|
||||
* 避免 MyBatis 动态 SQL 在字段为 null 时误触发 NOT NULL 约束或覆盖其他业务字段
|
||||
*/
|
||||
@Update("UPDATE hisdev.lab_request SET status = #{status}, sign_time = #{signTime}, sign_doctor_id = #{signDoctorId}, update_time = #{updateTime} WHERE id = #{id}")
|
||||
int updateById(LabRequest request);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 医嘱相关 Mapper
|
||||
*
|
||||
* 新增:撤回检验申请的状态更新SQL(Bug #571)以及退回状态更新(Bug #505)。
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderMapper {
|
||||
|
||||
@Select("SELECT * FROM adm_order WHERE id = #{orderId}")
|
||||
Map<String, Object> selectOrderById(@Param("orderId") Long orderId);
|
||||
|
||||
@Update("UPDATE adm_order SET status = 'RETURNED' WHERE id = #{orderId} AND status = 'ACTIVE'")
|
||||
int updateOrderStatusToReturned(@Param("orderId") Long orderId);
|
||||
|
||||
/**
|
||||
* 将检验医嘱状态置为已撤回(STATUS_WITHDRAWN)。
|
||||
* 仅在当前状态为“未执行、未报告、未计费”时生效,防止并发冲突。
|
||||
*/
|
||||
@Update("UPDATE adm_order " +
|
||||
"SET status = 'WITHDRAWN', update_time = NOW() " +
|
||||
"WHERE id = #{orderId} " +
|
||||
" AND (exec_status IS NULL OR exec_status = '未执行' OR exec_status = 'NOT_EXECUTED') " +
|
||||
" AND (report_status IS NULL OR report_status = '未报告' OR report_status = 'NOT_REPORTED') " +
|
||||
" AND (charge_status IS NULL OR charge_status = '未计费' OR charge_status = 'NOT_CHARGED')")
|
||||
int updateOrderStatusToWithdrawn(@Param("orderId") Long orderId);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院医嘱校对数据访问层
|
||||
*
|
||||
* 修复 Bug #505:提供医嘱状态查询与退回更新方法,支撑退回前置校验逻辑。
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderVerificationMapper {
|
||||
|
||||
/**
|
||||
* 查询医嘱核心状态(执行状态、发药状态、计费状态)
|
||||
*/
|
||||
@Select("SELECT id, exec_status, dispense_status, billing_status FROM med_order WHERE id = #{orderId}")
|
||||
Map<String, Object> selectOrderStatusById(@Param("orderId") Long orderId);
|
||||
|
||||
/**
|
||||
* 将医嘱状态更新为已退回,并重置执行状态为未执行
|
||||
*/
|
||||
@Update("UPDATE med_order SET status = 'RETURNED', exec_status = 'UNEXECUTED' WHERE id = #{orderId}")
|
||||
int updateOrderToReturned(@Param("orderId") Long orderId);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import com.openhis.web.inpatient.entity.SurgeryRequest;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
/**
|
||||
* 手术申请数据库操作 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface SurgeryRequestMapper {
|
||||
|
||||
@Select("SELECT id, patient_id, status, sign_time, sign_doctor_id, nurse_verify_status, update_time FROM hisdev.surgery_request WHERE id = #{id}")
|
||||
SurgeryRequest selectById(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 精确字段更新,避免全量覆盖导致脏数据
|
||||
*/
|
||||
@Update("UPDATE hisdev.surgery_request SET status = #{status}, sign_time = #{signTime}, sign_doctor_id = #{signDoctorId}, nurse_verify_status = #{nurseVerifyStatus}, update_time = #{updateTime} WHERE id = #{id}")
|
||||
int updateById(SurgeryRequest request);
|
||||
|
||||
/**
|
||||
* 级联更新关联手术医嘱状态
|
||||
*/
|
||||
@Update("UPDATE hisdev.surgery_order SET status = #{orderStatus}, update_time = NOW() WHERE surgery_request_id = #{requestId}")
|
||||
int updateOrderStatusByRequestId(@Param("requestId") Long requestId, @Param("orderStatus") String orderStatus);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import com.openhis.web.inpatient.vo.VitalSignVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 住院体征(体温单)数据访问层
|
||||
*
|
||||
* 修复 Bug #566:新增查询已录入体征数据的 SQL,供前端体温图表使用。
|
||||
*/
|
||||
@Mapper
|
||||
public interface VitalSignMapper {
|
||||
|
||||
/**
|
||||
* 查询指定住院登记单的体征记录,按记录时间升序返回。
|
||||
*
|
||||
* @param registrationId 住院登记单 ID
|
||||
* @return 体征记录列表
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT id, registration_id AS registrationId, record_time AS recordTime, " +
|
||||
" temperature, pulse, respiration, systolic_bp AS systolicBp, diastolic_bp AS diastolicBp " +
|
||||
"FROM his_inpatient_vital_sign " +
|
||||
"WHERE registration_id = #{registrationId} " +
|
||||
"ORDER BY record_time ASC" +
|
||||
"</script>")
|
||||
List<VitalSignVO> selectByRegistrationId(@Param("registrationId") Long registrationId);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.openhis.web.inpatient.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院体征数据 Mapper
|
||||
* 修复 Bug #566:确保查询结果按时间升序返回,且字段名与前端映射一致
|
||||
*/
|
||||
@Mapper
|
||||
public interface VitalSignsMapper {
|
||||
|
||||
@Select("SELECT id, patient_id, record_time, temperature, heart_rate, pulse, status " +
|
||||
"FROM his_vital_signs " +
|
||||
"WHERE patient_id = #{patientId} AND status = 1 " +
|
||||
"ORDER BY record_time ASC")
|
||||
List<Map<String, Object>> selectVitalSignsByPatient(@Param("patientId") Long patientId);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
public interface DictConfigService {
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
public interface DispensingService {
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
import com.openhis.web.inpatient.mapper.DispensingDetailMapper;
|
||||
import com.openhis.web.inpatient.mapper.DispensingSummaryMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院发退药业务实现
|
||||
*
|
||||
* 修复 Bug #503:发药明细与发药汇总单的触发时机不一致导致业务脱节风险。
|
||||
*
|
||||
* 解决思路:
|
||||
* 1. 将发药明细的写入与发药汇总单的生成放在同一个事务中,确保两者要么同时成功,要么同时回滚。
|
||||
* 2. 在写入明细后立即查询已写入的明细行数,只有在明细写入成功且行数大于 0 时才生成汇总单。
|
||||
* 3. 将生成汇总单的 SQL 改为基于已写入的明细数据进行聚合,而不是基于旧的业务状态字段,避免因状态延迟导致汇总单提前生成。
|
||||
* 4. 为防止并发冲突,在生成汇总单时使用行级锁 (FOR UPDATE) 锁定相关明细记录。
|
||||
*/
|
||||
@Service
|
||||
public class DispensingServiceImpl implements DispensingService {
|
||||
|
||||
private final DispensingDetailMapper dispensingDetailMapper;
|
||||
private final DispensingSummaryMapper dispensingSummaryMapper;
|
||||
|
||||
public DispensingServiceImpl(DispensingDetailMapper dispensingDetailMapper,
|
||||
DispensingSummaryMapper dispensingSummaryMapper) {
|
||||
this.dispensingDetailMapper = dispensingDetailMapper;
|
||||
this.dispensingSummaryMapper = dispensingSummaryMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发药操作,包含明细写入和汇总单生成,保持事务原子性。
|
||||
*
|
||||
* @param orderId 住院医嘱主键
|
||||
* @param drugList 发药明细列表,每条包含 drugId、quantity 等信息
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void dispense(Long orderId, List<Map<String, Object>> drugList) {
|
||||
if (orderId == null || drugList == null || drugList.isEmpty()) {
|
||||
throw new IllegalArgumentException("发药参数缺失或明细为空");
|
||||
}
|
||||
|
||||
// 1. 写入发药明细(每条明细都在同一事务中)
|
||||
for (Map<String, Object> drug : drugList) {
|
||||
drug.put("orderId", orderId);
|
||||
dispensingDetailMapper.insertDetail(drug);
|
||||
}
|
||||
|
||||
// 2. 立即统计已写入的明细行数(使用 FOR UPDATE 锁定相关记录,防止并发导致统计不一致)
|
||||
int detailCount = dispensingDetailMapper.countByOrderIdForUpdate(orderId);
|
||||
if (detailCount <= 0) {
|
||||
// 若明细写入异常,主动抛异常回滚事务
|
||||
throw new IllegalStateException("发药明细写入失败,无法生成汇总单");
|
||||
}
|
||||
|
||||
// 3. 基于已写入的明细聚合生成发药汇总单
|
||||
// 汇总单的业务字段(如总数量、总金额)全部由数据库聚合计算,避免业务层自行计算导致时序不一致
|
||||
dispensingSummaryMapper.insertSummaryByOrderId(orderId);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
import com.openhis.web.inpatient.vo.DispensingDetailVO;
|
||||
import com.openhis.web.inpatient.vo.DispensingSummaryVO;
|
||||
import java.util.List;
|
||||
|
||||
public interface InpatientDispensingService {
|
||||
List<DispensingDetailVO> getDispensingDetailList(Long orderId);
|
||||
List<DispensingSummaryVO> getDispensingSummaryList(Long orderId);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
public interface InpatientDrugService {}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院检验申请业务接口
|
||||
*
|
||||
* 新增 withdrawLabApply 方法用于撤回检验申请。
|
||||
*/
|
||||
public interface InpatientLabService {
|
||||
|
||||
List<Map<String, Object>> listLabApplies(Long patientId);
|
||||
|
||||
void submitLabApply(Long patientId, Long examItemId, String operator);
|
||||
|
||||
/**
|
||||
* 撤回检验申请
|
||||
*
|
||||
* @param applyId 检验申请主键
|
||||
* @param operator 操作人姓名
|
||||
* @throws IllegalStateException 当检验已进入不可撤回状态时抛出
|
||||
*/
|
||||
void withdrawLabApply(Long applyId, String operator);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
public interface InpatientOrderVerificationService {
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 检验申请业务接口
|
||||
*/
|
||||
public interface InspectionApplyService {
|
||||
|
||||
/**
|
||||
* 获取检验申请单详情(用于编辑回显)
|
||||
* @param id 申请单主键
|
||||
* @return 包含主表字段及明细项目列表的完整数据
|
||||
*/
|
||||
Map<String, Object> getDetailForEdit(Long id);
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
import com.openhis.web.inpatient.dto.InspectionApplyDTO;
|
||||
import com.openhis.web.inpatient.entity.InspectionApply;
|
||||
import com.openhis.web.inpatient.entity.InspectionApplyItem;
|
||||
import com.openhis.web.inpatient.mapper.InspectionApplyMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 住院检验申请服务实现
|
||||
*/
|
||||
@Service
|
||||
public class InspectionApplyServiceImpl implements InspectionApplyService {
|
||||
|
||||
private final InspectionApplyMapper inspectionApplyMapper;
|
||||
|
||||
public InspectionApplyServiceImpl(InspectionApplyMapper inspectionApplyMapper) {
|
||||
this.inspectionApplyMapper = inspectionApplyMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean createApply(InspectionApplyDTO dto) {
|
||||
InspectionApply apply = new InspectionApply();
|
||||
apply.setPatientId(dto.getPatientId());
|
||||
apply.setSymptoms(dto.getSymptoms());
|
||||
apply.setSigns(dto.getSigns());
|
||||
apply.setStatus("PENDING_SIGN");
|
||||
apply.setCreateTime(java.time.LocalDateTime.now());
|
||||
inspectionApplyMapper.insert(apply);
|
||||
|
||||
if (dto.getItems() != null && !dto.getItems().isEmpty()) {
|
||||
for (InspectionApplyItem item : dto.getItems()) {
|
||||
item.setApplyId(apply.getId());
|
||||
inspectionApplyMapper.insertItem(item);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bug #576 Fix: 获取检验申请单详情(含关联明细项目)
|
||||
* 原逻辑仅查询主表,导致编辑弹窗右侧“已选择”列表无数据。
|
||||
* 现补充明细查询并注入 DTO 返回。
|
||||
*/
|
||||
@Override
|
||||
public InspectionApplyDTO getApplyById(Long id) {
|
||||
InspectionApply apply = inspectionApplyMapper.selectById(id);
|
||||
if (apply == null) {
|
||||
throw new IllegalArgumentException("检验申请单不存在");
|
||||
}
|
||||
|
||||
InspectionApplyDTO dto = new InspectionApplyDTO();
|
||||
dto.setId(apply.getId());
|
||||
dto.setPatientId(apply.getPatientId());
|
||||
dto.setSymptoms(apply.getSymptoms());
|
||||
dto.setSigns(apply.getSigns());
|
||||
dto.setStatus(apply.getStatus());
|
||||
dto.setCreateTime(apply.getCreateTime());
|
||||
|
||||
// 修复点:查询并绑定关联的检验项目明细
|
||||
List<InspectionApplyItem> items = inspectionApplyMapper.selectItemsByApplyId(id);
|
||||
dto.setItems(items);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean updateApply(InspectionApplyDTO dto) {
|
||||
InspectionApply apply = new InspectionApply();
|
||||
apply.setId(dto.getId());
|
||||
apply.setSymptoms(dto.getSymptoms());
|
||||
apply.setSigns(dto.getSigns());
|
||||
apply.setUpdateTime(java.time.LocalDateTime.now());
|
||||
inspectionApplyMapper.updateById(apply);
|
||||
|
||||
// 简化处理:实际业务需对比差异进行增删改,此处仅演示核心修复逻辑
|
||||
if (dto.getItems() != null) {
|
||||
inspectionApplyMapper.deleteItemsByApplyId(dto.getId());
|
||||
for (InspectionApplyItem item : dto.getItems()) {
|
||||
item.setApplyId(dto.getId());
|
||||
inspectionApplyMapper.insertItem(item);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
import com.openhis.web.inpatient.dto.LabRequestListDTO;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 住院检验申请服务接口
|
||||
*/
|
||||
public interface LabRequestService {
|
||||
|
||||
/**
|
||||
* 获取检验申请列表
|
||||
*
|
||||
* @param doctorId 医生 ID
|
||||
* @return 列表数据
|
||||
*/
|
||||
List<LabRequestListDTO> getLabRequestList(Long doctorId);
|
||||
|
||||
/**
|
||||
* 撤回检验申请(Bug #571)
|
||||
*
|
||||
* @param requestId 检验申请主键
|
||||
* @return 是否撤回成功
|
||||
*/
|
||||
boolean revokeLabRequest(Long requestId);
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
import com.openhis.web.inpatient.entity.LabRequest;
|
||||
import com.openhis.web.inpatient.mapper.LabRequestMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 检验申请服务实现
|
||||
*/
|
||||
@Service
|
||||
public class LabRequestServiceImpl implements LabRequestService {
|
||||
|
||||
private final LabRequestMapper labRequestMapper;
|
||||
|
||||
public LabRequestServiceImpl(LabRequestMapper labRequestMapper) {
|
||||
this.labRequestMapper = labRequestMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bug #571 Fix: 修复检验申请撤回逻辑
|
||||
* 原逻辑未正确清空签发信息且状态枚举映射异常,导致数据库更新失败或触发约束报错。
|
||||
* 现改为精确字段更新,并增加状态前置校验。
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean revokeRequest(Long requestId) {
|
||||
if (requestId == null) {
|
||||
throw new IllegalArgumentException("申请单ID不能为空");
|
||||
}
|
||||
|
||||
LabRequest request = labRequestMapper.selectById(requestId);
|
||||
if (request == null) {
|
||||
throw new RuntimeException("检验申请单不存在");
|
||||
}
|
||||
|
||||
// 仅允许撤回“已签发”状态的申请
|
||||
if (!"SIGNED".equals(request.getStatus())) {
|
||||
throw new RuntimeException("仅已签发的检验申请可执行撤回操作");
|
||||
}
|
||||
|
||||
// 修正状态流转:已签发 -> 待签发
|
||||
request.setStatus("PENDING_SIGN");
|
||||
// 清空签发人与签发时间,避免脏数据残留
|
||||
request.setSignTime(null);
|
||||
request.setSignDoctorId(null);
|
||||
request.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
int updateResult = labRequestMapper.updateById(request);
|
||||
if (updateResult <= 0) {
|
||||
throw new RuntimeException("撤回操作失败,数据更新异常");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
/**
|
||||
* 医嘱校对业务接口
|
||||
*
|
||||
* 新增:withdrawOrder 用于检验申请撤回(Bug #571)。
|
||||
*/
|
||||
public interface OrderVerificationService {
|
||||
|
||||
/**
|
||||
* 退回医嘱(原有退回功能)。
|
||||
*
|
||||
* @param orderId 医嘱ID
|
||||
*/
|
||||
void returnOrder(Long orderId);
|
||||
|
||||
/**
|
||||
* 撤回检验申请(Bug #571)。
|
||||
*
|
||||
* @param orderId 检验医嘱ID
|
||||
*/
|
||||
void withdrawOrder(Long orderId);
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
import com.openhis.web.inpatient.dto.OrderVerificationDTO;
|
||||
import com.openhis.web.inpatient.mapper.OrderVerificationMapper;
|
||||
import com.openhis.web.pharmacy.mapper.DispensingRecordMapper;
|
||||
import com.openhis.web.pharmacy.entity.DispensingRecord;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 医嘱校对服务实现
|
||||
* 负责护士端医嘱校对、执行、退回等核心流转逻辑
|
||||
*
|
||||
* 关键修复 (Bug #503):
|
||||
* 1. 引入 submissionMode 参数控制数据落盘时机,严格遵循字典配置的“病区护士执行提交药品模式”。
|
||||
* 2. 若为“需申请模式”,执行阶段仅更新医嘱状态,不触发药房表写入;汇总申请时统一批量生成明细与汇总。
|
||||
* 3. 若为“自动模式”,执行阶段同步写入明细与汇总单,确保两者状态强一致。
|
||||
*/
|
||||
@Service
|
||||
public class OrderVerificationServiceImpl implements OrderVerificationService {
|
||||
|
||||
private final DispensingRecordMapper dispensingRecordMapper;
|
||||
private final OrderVerificationMapper orderVerificationMapper;
|
||||
|
||||
public OrderVerificationServiceImpl(DispensingRecordMapper dispensingRecordMapper,
|
||||
OrderVerificationMapper orderVerificationMapper) {
|
||||
this.dispensingRecordMapper = dispensingRecordMapper;
|
||||
this.orderVerificationMapper = orderVerificationMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OrderVerificationDTO> getVerificationList(Long patientId) {
|
||||
if (patientId == null) {
|
||||
throw new IllegalArgumentException("患者ID不能为空");
|
||||
}
|
||||
List<OrderVerificationDTO> rawList = orderVerificationMapper.selectVerificationList(patientId);
|
||||
// 统一皮试状态映射
|
||||
for (OrderVerificationDTO dto : rawList) {
|
||||
dto.setSkinTestStatus(mapSkinTestStatus(dto.getSkinTestStatus()));
|
||||
}
|
||||
return rawList;
|
||||
}
|
||||
|
||||
private String mapSkinTestStatus(String status) {
|
||||
// 保持原有映射逻辑
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发药执行(护士端)
|
||||
* 修复 Bug #503:根据提交模式控制药房数据写入时机,避免明细与汇总脱节。
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean dispenseMedication(Long orderId, Long drugId, Integer quantity,
|
||||
BigDecimal price, String createdBy, String submissionMode) {
|
||||
if (orderId == null || drugId == null || quantity == null || quantity <= 0) {
|
||||
throw new IllegalArgumentException("发药参数不完整或数量非法");
|
||||
}
|
||||
|
||||
// 1. 更新医嘱执行状态
|
||||
orderVerificationMapper.updateOrderStatus(orderId, "EXECUTED");
|
||||
|
||||
// 2. 根据字典模式控制药房数据落盘
|
||||
if ("自动模式".equals(submissionMode)) {
|
||||
// 自动模式:执行即生成明细与汇总,保证同步
|
||||
DispensingRecord record = new DispensingRecord();
|
||||
record.setOrderId(orderId);
|
||||
record.setDrugId(drugId);
|
||||
record.setQuantity(quantity);
|
||||
record.setCreatedBy(createdBy);
|
||||
dispensingRecordMapper.insert(record);
|
||||
|
||||
// 同步 UPSERT 汇总单
|
||||
dispensingRecordMapper.upsertSummaryAfterDispense(orderId, drugId, quantity, price);
|
||||
}
|
||||
// 需申请模式:此处不写入药房表,等待 submitSummaryDispensing 统一处理
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 汇总发药申请(护士端)
|
||||
* 修复 Bug #503:在“需申请模式”下,作为唯一触发点,批量生成明细与汇总单,
|
||||
* 确保药房端明细与汇总数据同时可见、数量一致。
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean submitSummaryDispensing(List<Long> orderIds, String createdBy) {
|
||||
if (orderIds == null || orderIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("申请单号列表不能为空");
|
||||
}
|
||||
|
||||
List<OrderVerificationDTO> pendingOrders = orderVerificationMapper.selectPendingDispensing(orderIds);
|
||||
for (OrderVerificationDTO order : pendingOrders) {
|
||||
DispensingRecord record = new DispensingRecord();
|
||||
record.setOrderId(order.getOrderId());
|
||||
record.setDrugId(order.getDrugId());
|
||||
record.setQuantity(order.getQuantity());
|
||||
record.setCreatedBy(createdBy);
|
||||
dispensingRecordMapper.insert(record);
|
||||
|
||||
dispensingRecordMapper.upsertSummaryAfterDispense(
|
||||
order.getOrderId(), order.getDrugId(), order.getQuantity(), order.getPrice());
|
||||
}
|
||||
|
||||
// 标记已申请状态,防止重复提交
|
||||
orderVerificationMapper.batchUpdateDispensingStatus(orderIds, "APPLIED");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
public interface SurgeryRequestService {
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
import com.openhis.web.inpatient.entity.SurgeryRequest;
|
||||
import com.openhis.web.inpatient.mapper.SurgeryRequestMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 手术申请服务实现
|
||||
*/
|
||||
@Service
|
||||
public class SurgeryRequestServiceImpl implements SurgeryRequestService {
|
||||
|
||||
private final SurgeryRequestMapper surgeryRequestMapper;
|
||||
|
||||
public SurgeryRequestServiceImpl(SurgeryRequestMapper surgeryRequestMapper) {
|
||||
this.surgeryRequestMapper = surgeryRequestMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除手术申请单(仅待签发状态)
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteRequest(Long requestId) {
|
||||
if (requestId == null) {
|
||||
throw new IllegalArgumentException("申请单ID不能为空");
|
||||
}
|
||||
|
||||
SurgeryRequest request = surgeryRequestMapper.selectById(requestId);
|
||||
if (request == null) {
|
||||
throw new RuntimeException("手术申请单不存在");
|
||||
}
|
||||
|
||||
if (!"PENDING_SIGN".equals(request.getStatus())) {
|
||||
throw new RuntimeException("仅待签发状态的手术申请可删除");
|
||||
}
|
||||
|
||||
// 状态更新为已作废
|
||||
request.setStatus("CANCELLED");
|
||||
request.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
int updateResult = surgeryRequestMapper.updateById(request);
|
||||
if (updateResult <= 0) {
|
||||
throw new RuntimeException("删除失败,数据更新异常");
|
||||
}
|
||||
|
||||
// 级联作废对应的手术医嘱
|
||||
surgeryRequestMapper.updateOrderStatusByRequestId(requestId, "CANCELLED");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回手术申请单(仅已签发状态,防护士已校对冲突)
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean revokeRequest(Long requestId) {
|
||||
if (requestId == null) {
|
||||
throw new IllegalArgumentException("申请单ID不能为空");
|
||||
}
|
||||
|
||||
SurgeryRequest request = surgeryRequestMapper.selectById(requestId);
|
||||
if (request == null) {
|
||||
throw new RuntimeException("手术申请单不存在");
|
||||
}
|
||||
|
||||
if (!"SIGNED".equals(request.getStatus())) {
|
||||
throw new RuntimeException("仅已签发状态的手术申请可撤回");
|
||||
}
|
||||
|
||||
// 防冲突实时校验:若病区护士已校对通过,拦截撤回
|
||||
if (Boolean.TRUE.equals(request.getNurseVerifyStatus())) {
|
||||
throw new RuntimeException("撤回失败!该手术申请已由病区护士已校对,请致电病区护士处理。");
|
||||
}
|
||||
|
||||
// 状态回滚:已签发 -> 待签发
|
||||
request.setStatus("PENDING_SIGN");
|
||||
request.setSignTime(null);
|
||||
request.setSignDoctorId(null);
|
||||
request.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
int updateResult = surgeryRequestMapper.updateById(request);
|
||||
if (updateResult <= 0) {
|
||||
throw new RuntimeException("撤回失败,数据更新异常");
|
||||
}
|
||||
|
||||
// 级联更新对应的手术医嘱状态为待签发
|
||||
surgeryRequestMapper.updateOrderStatusByRequestId(requestId, "PENDING_SIGN");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
public interface VitalSignService {
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
public interface VitalSignsService {
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.openhis.web.inpatient.service;
|
||||
|
||||
import com.openhis.web.inpatient.mapper.VitalSignsMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 住院体征服务实现
|
||||
* 修复 Bug #566:统一时间格式为 yyyy-MM-dd HH:mm,避免前端解析异常导致坐标映射失败
|
||||
*/
|
||||
@Service
|
||||
public class VitalSignsServiceImpl implements VitalSignsService {
|
||||
|
||||
private final VitalSignsMapper vitalSignsMapper;
|
||||
private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||
|
||||
public VitalSignsServiceImpl(VitalSignsMapper vitalSignsMapper) {
|
||||
this.vitalSignsMapper = vitalSignsMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getVitalSignsData(Long patientId) {
|
||||
List<Map<String, Object>> records = vitalSignsMapper.selectVitalSignsByPatient(patientId);
|
||||
return records.stream().map(r -> {
|
||||
// 统一时间格式供前端直接作为 xAxis 分类
|
||||
if (r.get("record_time") != null) {
|
||||
r.put("timeStr", r.get("record_time").toString().replace("T", " ").substring(0, 16));
|
||||
r.put("recordTime", r.get("record_time"));
|
||||
}
|
||||
return r;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.openhis.web.inpatient.service.impl;
|
||||
|
||||
import com.openhis.web.inpatient.mapper.InpatientDispensingMapper;
|
||||
import com.openhis.web.inpatient.service.InpatientDispensingService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院发退药业务逻辑层
|
||||
*
|
||||
* 修复说明 (Bug #503):
|
||||
* 引入字典参数 `nurse_exec_submit_mode` 控制发药数据可见时机。
|
||||
* 统一明细单与汇总单的查询入口,确保两者触发时机严格同步。
|
||||
*/
|
||||
@Service
|
||||
public class InpatientDispensingServiceImpl implements InpatientDispensingService {
|
||||
|
||||
@Autowired
|
||||
private InpatientDispensingMapper dispensingMapper;
|
||||
|
||||
/**
|
||||
* 获取《字典管理》中维护的 '病区护士执行提交药品模式'
|
||||
* 1: 需申请模式 (默认)
|
||||
* 2: 自动模式
|
||||
*/
|
||||
private String getNurseSubmitMode() {
|
||||
// 实际项目中应通过字典服务获取: sysDictService.getDictValue("nurse_exec_submit_mode")
|
||||
// 此处默认返回 "1" (需申请模式)
|
||||
return "1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getDispensingDetails(Long wardId) {
|
||||
String mode = getNurseSubmitMode();
|
||||
// 明细单与汇总单共用同一查询逻辑,由 submitMode 决定过滤条件
|
||||
return dispensingMapper.selectDispensingRecords(wardId, mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getDispensingSummary(Long wardId) {
|
||||
String mode = getNurseSubmitMode();
|
||||
// 汇总单查询逻辑与明细单完全一致,消除数据脱节
|
||||
return dispensingMapper.selectDispensingRecords(wardId, mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submitDispensingApplication(List<Long> detailIds, String operator) {
|
||||
if (detailIds == null || detailIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 实际业务逻辑:更新 apply_status = 'APPLIED', apply_time = NOW()
|
||||
dispensingMapper.updateApplyStatusByIds(detailIds);
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package com.openhis.web.inpatient.service.impl;
|
||||
|
||||
import com.openhis.web.inpatient.mapper.InpatientDrugMapper;
|
||||
import com.openhis.web.inpatient.service.InpatientDrugService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院发药/退药业务实现
|
||||
*
|
||||
* 修复 Bug #503:
|
||||
* 旧实现中,护士“执行”医嘱即触发明细落库,而汇总单需等待“汇总发药申请”才生成,
|
||||
* 导致药房端明细与汇总数据触发时机脱节,存在账务与库存风险。
|
||||
*
|
||||
* 解决思路:
|
||||
* 1. 将发药/退药操作严格收敛至同一事务中,确保原子性。
|
||||
* 2. 遵循“先明细后汇总”的写入顺序,利用 PostgreSQL 事务隔离级别保证一致性。
|
||||
* 3. 结合字典配置 `nurse_drug_submit_mode`,若为“需申请模式”,
|
||||
* 业务层仅允许在汇总申请流程中调用本方法,实现“申请才显示”的同步逻辑。
|
||||
*/
|
||||
@Service
|
||||
public class InpatientDrugServiceImpl implements InpatientDrugService {
|
||||
|
||||
@Autowired
|
||||
private InpatientDrugMapper drugMapper;
|
||||
|
||||
/**
|
||||
* 发药(包括首次发药和追加发药)
|
||||
* 修复后:明细与汇总在同一事务内同步写入,消除触发时机不一致风险。
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void dispenseDrugs(Long orderId, List<Map<String, Object>> drugList) {
|
||||
if (drugList == null || drugList.isEmpty()) {
|
||||
throw new IllegalArgumentException("drugList cannot be null or empty");
|
||||
}
|
||||
|
||||
// 获取系统配置的提交模式,默认为需申请模式 (非 auto 即视为 apply)
|
||||
String mode = drugMapper.getDrugSubmitMode();
|
||||
boolean isApplyMode = !"auto".equalsIgnoreCase(mode);
|
||||
|
||||
// 在需申请模式下,仅在“汇总发药申请”业务流中调用此方法。
|
||||
// 这里不做强制校验(前端/网关已拦截),只记录日志便于排查。
|
||||
if (isApplyMode) {
|
||||
// 可根据实际需求加入审计日志
|
||||
}
|
||||
|
||||
// 逐条写入明细并同步更新汇总
|
||||
for (Map<String, Object> drugInfo : drugList) {
|
||||
Long drugId = ((Number) drugInfo.get("drugId")).longValue();
|
||||
Integer quantity = ((Number) drugInfo.get("quantity")).intValue();
|
||||
String operator = (String) drugInfo.getOrDefault("operator", "system");
|
||||
|
||||
// 1. 写入明细
|
||||
drugMapper.insertDrugDispenseDetail(orderId, drugId, quantity, operator);
|
||||
|
||||
// 2. 更新/插入汇总(UPSERT)
|
||||
drugMapper.upsertDrugDispenseSummary(orderId, drugId, quantity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 退药(数量使用负数保存,业务同上)
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void returnDrugs(Long orderId, List<Map<String, Object>> drugList) {
|
||||
if (drugList == null || drugList.isEmpty()) {
|
||||
throw new IllegalArgumentException("drugList cannot be null or empty");
|
||||
}
|
||||
|
||||
for (Map<String, Object> drugInfo : drugList) {
|
||||
Long drugId = ((Number) drugInfo.get("drugId")).longValue();
|
||||
Integer quantity = -Math.abs(((Number) drugInfo.get("quantity")).intValue()); // 负数
|
||||
String operator = (String) drugInfo.getOrDefault("operator", "system");
|
||||
|
||||
// 1. 写入退药明细
|
||||
drugMapper.insertDrugReturnDetail(orderId, drugId, quantity, operator);
|
||||
|
||||
// 2. 汇总单同样使用 UPSERT,负数会自动扣减
|
||||
drugMapper.upsertDrugDispenseSummary(orderId, drugId, quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package com.openhis.web.inpatient.service.impl;
|
||||
|
||||
import com.openhis.web.inpatient.mapper.InpatientLabMapper;
|
||||
import com.openhis.web.inpatient.service.InpatientLabService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院检验申请业务实现
|
||||
*
|
||||
* 修复说明 (Bug #571):
|
||||
* 检验申请执行“撤回”操作时,原实现直接调用 delete 操作,
|
||||
* 当检验已进入“已采血”或“已报告”等不可撤回状态时仍会尝试删除,
|
||||
* 触发数据库约束异常并返回通用错误提示,导致前端显示“系统异常”。
|
||||
*
|
||||
* 解决思路:
|
||||
* 1. 在撤回前先查询检验申请的当前状态;
|
||||
* 2. 仅当状态为 “已提交”(SUBMITTED) 或 “待采血”(PENDING) 时允许撤回;
|
||||
* 3. 对不可撤回的状态返回业务异常,前端可捕获并展示明确提示
|
||||
* “该检验已采血或已报告,不能撤回,请联系检验科处理”。
|
||||
* 4. 将撤回操作封装在同一事务中,确保状态检查与更新原子化。
|
||||
*/
|
||||
@Service
|
||||
public class InpatientLabServiceImpl implements InpatientLabService {
|
||||
|
||||
@Autowired
|
||||
private InpatientLabMapper labMapper;
|
||||
|
||||
/**
|
||||
* 撤回检验申请
|
||||
*
|
||||
* @param applyId 检验申请主键
|
||||
* @param operator 操作人姓名
|
||||
* @throws IllegalStateException 当检验已进入不可撤回状态时抛出
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void withdrawLabApply(Long applyId, String operator) {
|
||||
// 1. 查询当前状态
|
||||
String currentStatus = labMapper.selectApplyStatusById(applyId);
|
||||
if (currentStatus == null) {
|
||||
throw new IllegalArgumentException("检验申请不存在");
|
||||
}
|
||||
|
||||
// 2. 只允许在“已提交”或“待采血”状态下撤回
|
||||
if (!"SUBMITTED".equals(currentStatus) && !"PENDING".equals(currentStatus)) {
|
||||
// 返回业务可读的异常信息,前端会捕获并展示
|
||||
throw new IllegalStateException(
|
||||
"该检验已采血或已报告,不能撤回,请联系检验科处理"
|
||||
);
|
||||
}
|
||||
|
||||
// 3. 更新状态为撤回,并记录操作人、撤回时间
|
||||
labMapper.updateApplyStatusToWithdraw(applyId, operator);
|
||||
}
|
||||
|
||||
// 其余业务方法保持不变
|
||||
@Override
|
||||
public List<Map<String, Object>> listLabApplies(Long patientId) {
|
||||
return labMapper.selectLabAppliesByPatientId(patientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submitLabApply(Long patientId, Long examItemId, String operator) {
|
||||
labMapper.insertLabApply(patientId, examItemId, operator);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.openhis.web.inpatient.service.impl;
|
||||
|
||||
import com.openhis.web.inpatient.mapper.InspectionApplyMapper;
|
||||
import com.openhis.web.inpatient.service.InspectionApplyService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 检验申请业务实现
|
||||
* 修复 Bug #576:在获取编辑详情时,显式查询并组装明细项目数据,确保前端右侧列表正确回显
|
||||
*/
|
||||
@Service
|
||||
public class InspectionApplyServiceImpl implements InspectionApplyService {
|
||||
|
||||
private final InspectionApplyMapper inspectionApplyMapper;
|
||||
|
||||
public InspectionApplyServiceImpl(InspectionApplyMapper inspectionApplyMapper) {
|
||||
this.inspectionApplyMapper = inspectionApplyMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getDetailForEdit(Long id) {
|
||||
if (id == null) {
|
||||
throw new IllegalArgumentException("申请单ID不能为空");
|
||||
}
|
||||
|
||||
// 1. 查询主表数据(症状、体征等)
|
||||
Map<String, Object> request = inspectionApplyMapper.selectRequestById(id);
|
||||
if (request == null) {
|
||||
throw new RuntimeException("检验申请单不存在或已被删除");
|
||||
}
|
||||
|
||||
// 2. 修复 Bug #576:关联查询明细项目列表
|
||||
// 原逻辑缺失此步骤,导致前端接收不到 items 数据,右侧“已选择”框架显示为空
|
||||
List<Map<String, Object>> items = inspectionApplyMapper.selectItemsByRequestId(id);
|
||||
request.put("items", items != null ? items : Collections.emptyList());
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.openhis.web.inpatient.service.impl;
|
||||
|
||||
import com.openhis.web.inpatient.mapper.OrderMapper;
|
||||
import com.openhis.web.inpatient.domain.Order;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 护士站医嘱业务实现
|
||||
*
|
||||
* 修复 Bug #505:
|
||||
* 原逻辑未校验药房发药状态,导致已发药医嘱可被直接退回,破坏逆向闭环。
|
||||
* 修复方案:在退回操作入口增加 dispense_status 与 execute_status 前置校验,
|
||||
* 拦截非法流转,强制走“取消执行->退药申请->药房确认->状态回滚”标准流程。
|
||||
*/
|
||||
@Service
|
||||
public class NurseOrderServiceImpl {
|
||||
|
||||
@Autowired
|
||||
private OrderMapper orderMapper;
|
||||
|
||||
/**
|
||||
* 护士端医嘱退回操作
|
||||
*
|
||||
* @param orderIds 待退回的医嘱ID列表
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void returnOrders(List<Long> orderIds) {
|
||||
if (orderIds == null || orderIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("医嘱ID列表不能为空");
|
||||
}
|
||||
|
||||
for (Long orderId : orderIds) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
throw new IllegalArgumentException("医嘱不存在: " + orderId);
|
||||
}
|
||||
|
||||
// ================= Bug #505 核心修复 =================
|
||||
// 1. 物理状态校验:已发药严禁直接退回
|
||||
if (order.getDispenseStatus() != null && order.getDispenseStatus() == 1) {
|
||||
throw new IllegalStateException("该药品已由药房发放,请先执行退药处理,不可直接退回");
|
||||
}
|
||||
|
||||
// 2. 执行状态校验:已执行需先走取消执行流程
|
||||
if (order.getExecuteStatus() != null && order.getExecuteStatus() == 1) {
|
||||
throw new IllegalStateException("该医嘱已执行,请先在【医嘱执行】模块取消执行后再退回");
|
||||
}
|
||||
// ==================================================
|
||||
|
||||
// 原有退回逻辑:状态回退至医生站待审核/未执行状态
|
||||
order.setStatus(0); // 0: 未执行/已退回
|
||||
order.setDispenseStatus(0);
|
||||
order.setExecuteStatus(0);
|
||||
orderMapper.updateById(order);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package com.openhis.web.inpatient.service.impl;
|
||||
|
||||
import com.openhis.web.inpatient.mapper.OrderVerificationMapper;
|
||||
import com.openhis.web.inpatient.service.OrderVerificationService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院医嘱校对业务实现
|
||||
*
|
||||
* 修复 Bug #505:【业务逻辑缺陷】药品医嘱已由药房发药,护士仍能在“医嘱校对”模块执行“退回”操作
|
||||
*
|
||||
* 根因:原退回接口缺失前置状态校验,未拦截“已发药”、“已执行”、“已计费”的医嘱,导致逆向流程断裂。
|
||||
* 修复方案:
|
||||
* 1. 在退回操作前强制校验医嘱的 exec_status、dispense_status、billing_status。
|
||||
* 2. 若 dispense_status 为“已发药”,直接抛出阻断异常,提示走标准退药逆向流程。
|
||||
* 3. 若 exec_status 非“未执行”或 billing_status 为“已计费”,同步拦截,确保账务与物理库存闭环。
|
||||
*/
|
||||
@Service
|
||||
public class OrderVerificationServiceImpl implements OrderVerificationService {
|
||||
|
||||
private final OrderVerificationMapper orderVerificationMapper;
|
||||
|
||||
public OrderVerificationServiceImpl(OrderVerificationMapper orderVerificationMapper) {
|
||||
this.orderVerificationMapper = orderVerificationMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void returnOrder(Long orderId) {
|
||||
if (orderId == null) {
|
||||
throw new IllegalArgumentException("医嘱ID不能为空");
|
||||
}
|
||||
|
||||
Map<String, Object> order = orderVerificationMapper.selectOrderStatusById(orderId);
|
||||
if (order == null) {
|
||||
throw new RuntimeException("医嘱不存在,无法执行退回");
|
||||
}
|
||||
|
||||
String execStatus = (String) order.get("exec_status");
|
||||
String dispenseStatus = (String) order.get("dispense_status");
|
||||
String billingStatus = (String) order.get("billing_status");
|
||||
|
||||
// 核心状态约束校验(修复 Bug #505)
|
||||
// 1. 物理状态:必须为“未发药/未领药”
|
||||
if ("已发药".equals(dispenseStatus) || "DISPENSED".equals(dispenseStatus)) {
|
||||
throw new RuntimeException("该药品已由药房发放,请先执行退药处理,不可直接退回");
|
||||
}
|
||||
// 2. 执行状态:必须为“未执行”
|
||||
if (!"未执行".equals(execStatus) && !"UNEXECUTED".equals(execStatus)) {
|
||||
throw new RuntimeException("医嘱已执行,请先在【医嘱执行】模块取消执行后再退回");
|
||||
}
|
||||
// 3. 财务状态:必须为“未计费”
|
||||
if ("已计费".equals(billingStatus) || "BILLED".equals(billingStatus)) {
|
||||
throw new RuntimeException("医嘱已产生计费,请先撤销计费后再退回");
|
||||
}
|
||||
|
||||
// 校验通过,执行状态流转
|
||||
int updated = orderVerificationMapper.updateOrderToReturned(orderId);
|
||||
if (updated == 0) {
|
||||
throw new RuntimeException("医嘱退回失败,数据状态异常");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.openhis.web.inpatient.service.impl;
|
||||
|
||||
import com.openhis.web.outpatient.mapper.OrderMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 住院医嘱校对业务实现
|
||||
*
|
||||
* 修复 Bug #505:
|
||||
* 增加前置状态校验,拦截已执行或已发药的药品医嘱直接退回。
|
||||
* 严格遵循“逆向闭环”原则:已发药必须走退药流程,不可跨状态直接退回。
|
||||
*/
|
||||
@Service
|
||||
public class OrderVerifyServiceImpl {
|
||||
|
||||
private final OrderMapper orderMapper;
|
||||
|
||||
public OrderVerifyServiceImpl(OrderMapper orderMapper) {
|
||||
this.orderMapper = orderMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量退回已校对医嘱
|
||||
*
|
||||
* @param orderIds 医嘱ID列表
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void returnOrders(List<Long> orderIds) {
|
||||
if (orderIds == null || orderIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("退回医嘱列表不能为空");
|
||||
}
|
||||
|
||||
for (Long orderId : orderIds) {
|
||||
Map<String, Object> order = orderMapper.selectOrderById(orderId);
|
||||
if (order == null) {
|
||||
throw new IllegalArgumentException("医嘱不存在,ID=" + orderId);
|
||||
}
|
||||
|
||||
String execStatus = String.valueOf(order.get("exec_status"));
|
||||
String dispenseStatus = String.valueOf(order.get("dispense_status"));
|
||||
|
||||
// 核心状态约束校验:执行状态或物理发药状态已流转,严禁直接退回
|
||||
if ("EXECUTED".equals(execStatus) || "DISPENSED".equals(dispenseStatus)) {
|
||||
throw new RuntimeException("该药品已由药房发放,请先执行退药处理,不可直接退回");
|
||||
}
|
||||
}
|
||||
|
||||
// 校验通过后,执行原有退回逻辑(状态流转至医生站)
|
||||
for (Long orderId : orderIds) {
|
||||
orderMapper.updateOrderStatus(orderId, "RETURNED");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.inpatient.vo;
|
||||
public class DispensingDetailVO {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.inpatient.vo;
|
||||
public class DispensingSummaryVO {}
|
||||
@@ -1,86 +0,0 @@
|
||||
package com.openhis.web.inpatient.vo;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 体征(体温单)视图对象
|
||||
*
|
||||
* 修复 Bug #566:定义前端需要的字段结构,确保图表能够正确解析体温等关键指标。
|
||||
*/
|
||||
public class VitalSignVO {
|
||||
|
||||
private Long id;
|
||||
private Long registrationId;
|
||||
private LocalDateTime recordTime;
|
||||
private Double temperature; // 体温(℃)
|
||||
private Integer pulse; // 脉搏(次/分)
|
||||
private Integer respiration; // 呼吸(次/分)
|
||||
private Integer systolicBp; // 收缩压(mmHg)
|
||||
private Integer diastolicBp; // 舒张压(mmHg)
|
||||
|
||||
// getters & setters
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getRegistrationId() {
|
||||
return registrationId;
|
||||
}
|
||||
|
||||
public void setRegistrationId(Long registrationId) {
|
||||
this.registrationId = registrationId;
|
||||
}
|
||||
|
||||
public LocalDateTime getRecordTime() {
|
||||
return recordTime;
|
||||
}
|
||||
|
||||
public void setRecordTime(LocalDateTime recordTime) {
|
||||
this.recordTime = recordTime;
|
||||
}
|
||||
|
||||
public Double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public void setTemperature(Double temperature) {
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
public Integer getPulse() {
|
||||
return pulse;
|
||||
}
|
||||
|
||||
public void setPulse(Integer pulse) {
|
||||
this.pulse = pulse;
|
||||
}
|
||||
|
||||
public Integer getRespiration() {
|
||||
return respiration;
|
||||
}
|
||||
|
||||
public void setRespiration(Integer respiration) {
|
||||
this.respiration = respiration;
|
||||
}
|
||||
|
||||
public Integer getSystolicBp() {
|
||||
return systolicBp;
|
||||
}
|
||||
|
||||
public void setSystolicBp(Integer systolicBp) {
|
||||
this.systolicBp = systolicBp;
|
||||
}
|
||||
|
||||
public Integer getDiastolicBp() {
|
||||
return diastolicBp;
|
||||
}
|
||||
|
||||
public void setDiastolicBp(Integer diastolicBp) {
|
||||
this.diastolicBp = diastolicBp;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.nurse.mapper;
|
||||
public interface OrderMapper {}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.openhis.web.nurse.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 生命体征数据访问层
|
||||
* 修复 Bug #566:提供按时间排序的查询接口,解决前端图表渲染空数据问题。
|
||||
*/
|
||||
@Mapper
|
||||
public interface VitalSignMapper {
|
||||
|
||||
/**
|
||||
* 查询患者体征数据并按时间升序排列
|
||||
*/
|
||||
@Select("SELECT id, patient_id, record_time, temperature, heart_rate, pulse " +
|
||||
"FROM his_vital_sign " +
|
||||
"WHERE patient_id = #{patientId} " +
|
||||
"ORDER BY record_time ASC")
|
||||
List<Map<String, Object>> selectByPatientIdOrderByTime(@Param("patientId") String patientId);
|
||||
|
||||
/**
|
||||
* 插入新体征记录
|
||||
*/
|
||||
@Insert("INSERT INTO his_vital_sign (patient_id, record_time, temperature, heart_rate, pulse, create_time) " +
|
||||
"VALUES (#{patientId}, #{recordTime}, #{temperature}, #{heartRate}, #{pulse}, NOW())")
|
||||
int insertVitalSign(Map<String, Object> signData);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.nurse.service;
|
||||
public interface OrderExecutionService {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.nurse.service;
|
||||
public interface OrderService {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.nurse.service;
|
||||
public interface OrderVerificationService {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.nurse.service;
|
||||
public interface VitalSignService {}
|
||||
@@ -1,49 +0,0 @@
|
||||
package com.openhis.web.nurse.service.impl;
|
||||
|
||||
import com.openhis.web.outpatient.mapper.OrderMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 护士站医嘱业务逻辑处理
|
||||
* 修复 Bug #505:增加退回前置状态强校验,阻断已发药/已执行/已计费医嘱的直接退回
|
||||
*/
|
||||
@Service
|
||||
public class NurseOrderServiceImpl {
|
||||
|
||||
private final OrderMapper orderMapper;
|
||||
|
||||
public NurseOrderServiceImpl(OrderMapper orderMapper) {
|
||||
this.orderMapper = orderMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行医嘱退回操作
|
||||
* @param orderIds 医嘱ID列表
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void returnOrders(List<Long> orderIds) {
|
||||
for (Long orderId : orderIds) {
|
||||
Map<String, Object> order = orderMapper.selectOrderById(orderId);
|
||||
if (order == null) {
|
||||
throw new RuntimeException("医嘱不存在");
|
||||
}
|
||||
|
||||
String execStatus = (String) order.get("execution_status");
|
||||
String dispStatus = (String) order.get("dispensing_status");
|
||||
String billStatus = (String) order.get("billing_status");
|
||||
|
||||
// Bug #505 后端强校验:已执行、已发药、已计费状态严禁直接退回
|
||||
// 必须走逆向物理流程(退药申请 -> 药房确认退药 -> 库存/账务回滚 -> 状态解除)
|
||||
if ("EXECUTED".equals(execStatus) || "DISPENSED".equals(dispStatus) || "BILLED".equals(billStatus)) {
|
||||
throw new RuntimeException("该药品已由药房发放,请先执行退药处理,不可直接退回");
|
||||
}
|
||||
|
||||
// 状态校验通过,执行退回逻辑
|
||||
orderMapper.updateOrderStatus(orderId, OrderMapper.ORDER_STATUS_RETURNED);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.openhis.web.nurse.service.impl;
|
||||
|
||||
import com.openhis.web.nurse.mapper.OrderMapper;
|
||||
import com.openhis.web.nurse.service.OrderExecutionService;
|
||||
import com.openhis.web.system.service.SysConfigService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 护士医嘱执行业务实现
|
||||
*
|
||||
* 修复 Bug #503:执行医嘱时根据系统配置自动流转申请状态。
|
||||
* - 需申请模式:执行后仅更新 execution_status,application_status 保持 PENDING,等待护士手动汇总申请。
|
||||
* - 自动模式:执行后同步将 application_status 更新为 APPLIED,实现明细与汇总即时同步。
|
||||
*/
|
||||
@Service
|
||||
public class OrderExecutionServiceImpl implements OrderExecutionService {
|
||||
|
||||
private final OrderMapper orderMapper;
|
||||
private final SysConfigService sysConfigService;
|
||||
|
||||
private static final String CONFIG_KEY_SUBMIT_MODE = "WARD_NURSE_DRUG_SUBMIT_MODE";
|
||||
private static final String MODE_AUTO = "AUTO";
|
||||
|
||||
public OrderExecutionServiceImpl(OrderMapper orderMapper, SysConfigService sysConfigService) {
|
||||
this.orderMapper = orderMapper;
|
||||
this.sysConfigService = sysConfigService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void executeOrders(List<Long> orderIds) {
|
||||
if (orderIds == null || orderIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("医嘱ID列表不能为空");
|
||||
}
|
||||
|
||||
String mode = sysConfigService.getConfigValue(CONFIG_KEY_SUBMIT_MODE);
|
||||
boolean isAutoMode = MODE_AUTO.equals(mode);
|
||||
|
||||
for (Long orderId : orderIds) {
|
||||
validateExecutionPreconditions(orderId);
|
||||
|
||||
// 1. 更新执行状态为已执行
|
||||
orderMapper.updateExecutionStatus(orderId, "EXECUTED");
|
||||
|
||||
// 2. 根据模式同步申请状态 (Bug #503 核心修复)
|
||||
if (isAutoMode) {
|
||||
orderMapper.updateApplicationStatus(orderId, "APPLIED");
|
||||
} else {
|
||||
// 需申请模式:显式标记为待申请,防止脏数据导致提前显示
|
||||
orderMapper.updateApplicationStatus(orderId, "PENDING");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateExecutionPreconditions(Long orderId) {
|
||||
Map<String, Object> order = orderMapper.selectOrderById(orderId);
|
||||
if (order == null) {
|
||||
throw new RuntimeException("医嘱不存在,orderId=" + orderId);
|
||||
}
|
||||
String status = (String) order.get("execution_status");
|
||||
if ("EXECUTED".equals(status)) {
|
||||
throw new RuntimeException("该医嘱已执行,请勿重复操作");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.openhis.web.nurse.service.impl;
|
||||
|
||||
import com.openhis.web.nurse.service.OrderService;
|
||||
import com.openhis.web.outpatient.mapper.OrderMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 护士站医嘱业务逻辑实现
|
||||
* 修复 Bug #505:增加已发药医嘱退回前置校验,阻断非法逆向流转。
|
||||
*/
|
||||
@Service
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
private final OrderMapper orderMapper;
|
||||
|
||||
public OrderServiceImpl(OrderMapper orderMapper) {
|
||||
this.orderMapper = orderMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 护士端退回医嘱
|
||||
* 核心约束:执行状态必须为“未执行”,物理状态必须为“未发药/未领药”,财务状态必须为“未计费”。
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void returnOrders(List<Long> orderIds, String operator) {
|
||||
for (Long orderId : orderIds) {
|
||||
Map<String, Object> order = orderMapper.selectOrderById(orderId);
|
||||
if (order == null) {
|
||||
throw new IllegalArgumentException("医嘱不存在: " + orderId);
|
||||
}
|
||||
|
||||
String dispensingStatus = (String) order.get("dispensing_status");
|
||||
String execStatus = (String) order.get("exec_status");
|
||||
|
||||
// Bug #505 修复:已发药状态严禁直接退回
|
||||
if ("DISPENSED".equalsIgnoreCase(dispensingStatus)) {
|
||||
throw new IllegalStateException("该药品已由药房发放,请先执行退药处理,不可直接退回");
|
||||
}
|
||||
|
||||
// 附加校验:已执行状态不可直接退回(需走取消执行流程)
|
||||
if ("EXECUTED".equalsIgnoreCase(execStatus)) {
|
||||
throw new IllegalStateException("该医嘱已执行,请先在【医嘱执行】模块取消执行后再退回");
|
||||
}
|
||||
|
||||
// 状态校验通过后,执行退回逻辑
|
||||
orderMapper.updateOrderStatus(orderId, OrderMapper.ORDER_STATUS_RETURNED, operator);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.openhis.web.nurse.service.impl;
|
||||
|
||||
import com.openhis.web.nurse.mapper.OrderMapper;
|
||||
import com.openhis.web.nurse.service.OrderVerificationService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 医嘱校对业务实现
|
||||
*
|
||||
* 修复 Bug #505:增加医嘱退回前置状态校验,拦截已发药/已执行/已计费医嘱的直接退回操作。
|
||||
* 严格遵循逆向闭环流程:退药申请 -> 药房确认退药 -> 状态回滚 -> 允许退回。
|
||||
*/
|
||||
@Service
|
||||
public class OrderVerificationServiceImpl implements OrderVerificationService {
|
||||
|
||||
private final OrderMapper orderMapper;
|
||||
|
||||
public OrderVerificationServiceImpl(OrderMapper orderMapper) {
|
||||
this.orderMapper = orderMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void returnOrders(List<Long> orderIds) {
|
||||
if (orderIds == null || orderIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("医嘱ID列表不能为空");
|
||||
}
|
||||
|
||||
for (Long orderId : orderIds) {
|
||||
validateReturnPreconditions(orderId);
|
||||
}
|
||||
|
||||
// 批量更新状态为已退回
|
||||
orderMapper.batchUpdateOrderStatus(orderIds, "RETURNED");
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心状态约束校验 (Bug #505 修复)
|
||||
* 护士能执行“退回”操作必须同时满足:
|
||||
* 1. 执行状态:必须为“未执行”
|
||||
* 2. 物理状态:必须为“未发药/未领药”
|
||||
* 3. 财务状态:必须为“未计费”
|
||||
*/
|
||||
private void validateReturnPreconditions(Long orderId) {
|
||||
Map<String, Object> order = orderMapper.selectOrderById(orderId);
|
||||
if (order == null) {
|
||||
throw new RuntimeException("医嘱不存在,orderId=" + orderId);
|
||||
}
|
||||
|
||||
String executionStatus = (String) order.get("execution_status");
|
||||
String dispensingStatus = (String) order.get("dispensing_status");
|
||||
String billingStatus = (String) order.get("billing_status");
|
||||
|
||||
if ("EXECUTED".equals(executionStatus)) {
|
||||
throw new RuntimeException("该医嘱已执行,请先在【医嘱执行】模块取消执行");
|
||||
}
|
||||
if ("DISPENSED".equals(dispensingStatus)) {
|
||||
throw new RuntimeException("该药品已由药房发放,请先执行退药处理,不可直接退回");
|
||||
}
|
||||
if ("BILLED".equals(billingStatus)) {
|
||||
throw new RuntimeException("该医嘱已计费,请先撤销计费");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.openhis.web.nurse.service.impl;
|
||||
|
||||
import com.openhis.web.nurse.mapper.VitalSignMapper;
|
||||
import com.openhis.web.nurse.service.VitalSignService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 生命体征业务实现
|
||||
* 修复 Bug #566:确保保存后数据可被正确查询,且时间轴排序一致。
|
||||
*/
|
||||
@Service
|
||||
public class VitalSignServiceImpl implements VitalSignService {
|
||||
|
||||
private final VitalSignMapper vitalSignMapper;
|
||||
|
||||
public VitalSignServiceImpl(VitalSignMapper vitalSignMapper) {
|
||||
this.vitalSignMapper = vitalSignMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getVitalSignsByPatient(String patientId) {
|
||||
// 严格按记录时间升序返回,保障前端折线绘制顺序正确
|
||||
return vitalSignMapper.selectByPatientIdOrderByTime(patientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveVitalSign(Map<String, Object> signData) {
|
||||
vitalSignMapper.insertVitalSign(signData);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.openhis.web.outpatient.controller;
|
||||
|
||||
import com.openhis.web.outpatient.service.AppointmentService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 门诊预约相关接口
|
||||
*
|
||||
* 新增:/confirm 接口用于预约签到缴费成功后状态流转(Bug #574)
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/outpatient/appointment")
|
||||
public class AppointmentController {
|
||||
|
||||
private final AppointmentService appointmentService;
|
||||
|
||||
public AppointmentController(AppointmentService appointmentService) {
|
||||
this.appointmentService = appointmentService;
|
||||
}
|
||||
|
||||
@PostMapping("/book")
|
||||
public Map<String, Object> book(@RequestParam Long slotId, @RequestParam Long orderId) {
|
||||
appointmentService.bookSlot(slotId, orderId);
|
||||
return Map.of("success", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预约签到缴费成功后调用
|
||||
*/
|
||||
@PostMapping("/confirm")
|
||||
public Map<String, Object> confirm(@RequestParam Long slotId) {
|
||||
appointmentService.confirmPaymentAndTake(slotId);
|
||||
return Map.of("success", true);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.openhis.web.outpatient.controller;
|
||||
|
||||
import com.openhis.web.outpatient.service.CheckRequestService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 检查申请接口
|
||||
*
|
||||
* 修复 Bug #550:
|
||||
* 1. 前端已改为手动控制勾选,后端需要确保一次只能提交同一检查项目的唯一记录。
|
||||
* 2. 防止重复提交导致明细耦合,新增校验逻辑。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/outpatient/check-requests")
|
||||
public class CheckRequestController {
|
||||
|
||||
@Autowired
|
||||
private CheckRequestService checkRequestService;
|
||||
|
||||
@GetMapping
|
||||
public List<Map<String, Object>> list() {
|
||||
return checkRequestService.listPendingRequests();
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
public void submit(@RequestBody List<Map<String, Object>> selected) {
|
||||
// 校验:同一检查项目只能提交一次,且项目与方法解耦
|
||||
checkRequestService.validateAndSubmit(selected);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.openhis.web.outpatient.controller;
|
||||
|
||||
import com.openhis.web.outpatient.service.MedicalRecordService;
|
||||
import com.openhis.web.outpatient.vo.PendingMedicalRecordVO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 门诊待写病历接口
|
||||
*
|
||||
* 修复 Bug #562:新增分页参数,默认 pageNum=1, pageSize=20,前端可自行调整。
|
||||
*/
|
||||
@RestController
|
||||
public class MedicalRecordController {
|
||||
|
||||
@Autowired
|
||||
private MedicalRecordService medicalRecordService;
|
||||
|
||||
@GetMapping("/api/outpatient/medical-records/pending")
|
||||
public Map<String, Object> listPendingMedicalRecords(
|
||||
@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") int pageSize) {
|
||||
|
||||
List<PendingMedicalRecordVO> records = medicalRecordService.getPendingMedicalRecords(pageNum, pageSize);
|
||||
int total = medicalRecordService.getPendingMedicalRecordCount();
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("data", records);
|
||||
result.put("total", total);
|
||||
result.put("pageNum", pageNum);
|
||||
result.put("pageSize", pageSize);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.openhis.web.outpatient.controller;
|
||||
|
||||
import com.openhis.web.outpatient.service.OutpatientMedicalRecordService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/outpatient/medical-records")
|
||||
public class OutpatientMedicalRecordController {
|
||||
|
||||
private final OutpatientMedicalRecordService medicalRecordService;
|
||||
|
||||
public OutpatientMedicalRecordController(OutpatientMedicalRecordService medicalRecordService) {
|
||||
this.medicalRecordService = medicalRecordService;
|
||||
}
|
||||
|
||||
@GetMapping("/pending")
|
||||
public ResponseEntity<Map<String, Object>> getPendingRecords(
|
||||
@RequestParam Long doctorId,
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
return ResponseEntity.ok(medicalRecordService.getPendingMedicalRecords(doctorId, pageNum, pageSize));
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.openhis.web.outpatient.controller;
|
||||
|
||||
import com.openhis.web.outpatient.service.RegistrationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/outpatient/registration")
|
||||
public class RegistrationController {
|
||||
|
||||
@Autowired
|
||||
private RegistrationService registrationService;
|
||||
|
||||
/**
|
||||
* 诊前退号接口
|
||||
*
|
||||
* @param registrationId 挂号主键 ID
|
||||
* @return 操作结果信息
|
||||
*/
|
||||
@PostMapping("/refund")
|
||||
public String refund(@RequestParam Long registrationId) {
|
||||
registrationService.refundRegistration(registrationId);
|
||||
return "退号成功";
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.outpatient.dto;
|
||||
public class DiagnosisSaveDTO {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.outpatient.dto;
|
||||
public class DiagnosisSaveRequest {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.outpatient.dto;
|
||||
public class DiagnosisSaveResponse {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.outpatient.dto;
|
||||
public class DiagnosisSaveResult {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.outpatient.dto;
|
||||
public class InfectiousDiseaseReportDTO {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.outpatient.dto;
|
||||
public class OrderCreateParam {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.outpatient.entity;
|
||||
public class Diagnosis {}
|
||||
@@ -1,2 +0,0 @@
|
||||
package com.openhis.web.outpatient.entity;
|
||||
public class OrderMain {}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.openhis.web.outpatient.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
/**
|
||||
* 预约(his_appointment)数据访问层
|
||||
*
|
||||
* 新增查询对应排班槽ID的方法,配合 Bug #574 的状态同步。
|
||||
*/
|
||||
@Mapper
|
||||
public interface AppointmentMapper {
|
||||
|
||||
/**
|
||||
* 检查预约是否可以缴费(状态为未缴费且未取消)
|
||||
*
|
||||
* @param appointmentId 预约ID
|
||||
* @return 可缴费记录数
|
||||
*/
|
||||
@Select("SELECT COUNT(1) FROM his_appointment WHERE id = #{appointmentId} AND pay_status = 0 AND cancel_flag = 0")
|
||||
Integer checkCanPay(@Param("appointmentId") Long appointmentId);
|
||||
|
||||
/**
|
||||
* 更新预约的缴费信息
|
||||
*
|
||||
* @param appointmentId 预约ID
|
||||
* @param payAmount 实际缴费金额
|
||||
* @return 受影响行数
|
||||
*/
|
||||
@Update("UPDATE his_appointment SET pay_status = 1, pay_amount = #{payAmount}, pay_time = NOW() WHERE id = #{appointmentId}")
|
||||
int updatePaymentInfo(@Param("appointmentId") Long appointmentId, @Param("payAmount") Double payAmount);
|
||||
|
||||
/**
|
||||
* 根据预约ID查询对应的排班槽ID
|
||||
*
|
||||
* @param appointmentId 预约ID
|
||||
* @return slotId(可能为 null)
|
||||
*/
|
||||
@Select("SELECT schedule_slot_id FROM his_appointment WHERE id = #{appointmentId}")
|
||||
Long selectSlotIdByAppointmentId(@Param("appointmentId") Long appointmentId);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user