revert: restore develop to clean baseline 5132de36 (remove all AI changes)

This commit is contained in:
2026-05-28 09:43:49 +08:00
parent bdec44d6c5
commit 913a971ce4
481 changed files with 3036 additions and 26749 deletions

View File

@@ -1,2 +0,0 @@
package com.openhis.web.appointment.dto;
public class AppointmentParam {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.appointment.entity;
public class Appointment {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.appointment.mapper;
public interface AppointmentMapper {}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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: 退号时扣减已预约数并累加版本号
* 严格遵循 PRDbooked_num - 1version + 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);
}

View File

@@ -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);
}

View File

@@ -1,4 +0,0 @@
package com.openhis.web.appointment.service;
public interface AppointmentService {
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.doctorstation.dto;
public class MedicalRecordListDTO {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.doctorstation.dto;
public class MedicalRecordQueryParam {}

View File

@@ -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);
}

View File

@@ -1,4 +0,0 @@
package com.openhis.web.doctorstation.service;
public interface MedicalRecordService {
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.inpatient.domain;
public class Order {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.inpatient.dto;
public class InspectionApplyDTO {}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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 省略 ...
}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.inpatient.entity;
public class InpatientOrder {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.inpatient.entity;
public class InspectionApply {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.inpatient.entity;
public class InspectionApplyItem {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.inpatient.entity;
public class LabRequest {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.inpatient.entity;
public class SurgeryRequest {}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.inpatient.mapper;
public interface InpatientOrderMapper {}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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
*
* 新增撤回检验申请的状态更新SQLBug #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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -1,4 +0,0 @@
package com.openhis.web.inpatient.service;
public interface DictConfigService {
}

View File

@@ -1,4 +0,0 @@
package com.openhis.web.inpatient.service;
public interface DispensingService {
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.inpatient.service;
public interface InpatientDrugService {}

View File

@@ -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);
}

View File

@@ -1,4 +0,0 @@
package com.openhis.web.inpatient.service;
public interface InpatientOrderVerificationService {
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -1,4 +0,0 @@
package com.openhis.web.inpatient.service;
public interface SurgeryRequestService {
}

View File

@@ -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;
}
}

View File

@@ -1,4 +0,0 @@
package com.openhis.web.inpatient.service;
public interface VitalSignService {
}

View File

@@ -1,4 +0,0 @@
package com.openhis.web.inpatient.service;
public interface VitalSignsService {
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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("医嘱退回失败,数据状态异常");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.inpatient.vo;
public class DispensingDetailVO {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.inpatient.vo;
public class DispensingSummaryVO {}

View File

@@ -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;
}
}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.nurse.mapper;
public interface OrderMapper {}

View File

@@ -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);
}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.nurse.service;
public interface OrderExecutionService {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.nurse.service;
public interface OrderService {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.nurse.service;
public interface OrderVerificationService {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.nurse.service;
public interface VitalSignService {}

View File

@@ -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);
}
}
}

View File

@@ -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_statusapplication_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("该医嘱已执行,请勿重复操作");
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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("该医嘱已计费,请先撤销计费");
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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 "退号成功";
}
}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.outpatient.dto;
public class DiagnosisSaveDTO {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.outpatient.dto;
public class DiagnosisSaveRequest {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.outpatient.dto;
public class DiagnosisSaveResponse {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.outpatient.dto;
public class DiagnosisSaveResult {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.outpatient.dto;
public class InfectiousDiseaseReportDTO {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.outpatient.dto;
public class OrderCreateParam {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.outpatient.entity;
public class Diagnosis {}

View File

@@ -1,2 +0,0 @@
package com.openhis.web.outpatient.entity;
public class OrderMain {}

View File

@@ -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