diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/controller/CheckTypeController.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/controller/CheckTypeController.java index 3dd4a911..6ff69a4e 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/controller/CheckTypeController.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/controller/CheckTypeController.java @@ -6,11 +6,14 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.core.common.core.controller.BaseController; import com.core.common.core.domain.AjaxResult; import com.core.common.core.domain.R; +import com.core.common.utils.SecurityUtils; import com.openhis.check.domain.CheckMethod; import com.openhis.check.domain.CheckPackage; +import com.openhis.check.domain.CheckPackageDetail; import com.openhis.check.domain.CheckPart; import com.openhis.check.domain.CheckType; import com.openhis.check.service.ICheckMethodService; +import com.openhis.check.service.ICheckPackageDetailService; import com.openhis.check.service.ICheckPackageService; import com.openhis.check.service.ICheckPartService; import com.openhis.check.service.ICheckTypeService; @@ -33,7 +36,7 @@ import java.util.stream.Collectors; * @updated 2025-11-26 - 增加套餐设置相关接口 */ @RestController -@RequestMapping({"/system/check-type", "/system"}) +@RequestMapping({ "/system/check-type", "/system" }) @Slf4j @AllArgsConstructor public class CheckTypeController extends BaseController { @@ -42,6 +45,7 @@ public class CheckTypeController extends BaseController { private final ICheckMethodService checkMethodService; private final ICheckPartService checkPartService; private final ICheckPackageService checkPackageService; + private final ICheckPackageDetailService checkPackageDetailService; private final ICheckPackageAppService checkPackageAppService; /** @@ -52,15 +56,15 @@ public class CheckTypeController extends BaseController { @RequestParam(defaultValue = "1") Integer pageNo, @RequestParam(defaultValue = "10") Integer pageSize) { - if (pageSize > 10) pageSize = 10; + if (pageSize > 10) + pageSize = 10; // 1. 分页查询父节点(NULL + 0 都算父) Page parentPage = checkTypeService.page( new Page<>(pageNo, pageSize), new QueryWrapper() .and(w -> w.isNull("parent_id").or().eq("parent_id", 0)) - .orderByAsc("id") - ); + .orderByAsc("id")); if (parentPage.getRecords().isEmpty()) { return AjaxResult.success(parentPage); @@ -74,12 +78,10 @@ public class CheckTypeController extends BaseController { // 3. 查询子节点 List children = checkTypeService.list( - new QueryWrapper().in("parent_id", parentIds) - ); + new QueryWrapper().in("parent_id", parentIds)); // 4. 分组 - Map> childMap = - children.stream().collect(Collectors.groupingBy(CheckType::getParentId)); + Map> childMap = children.stream().collect(Collectors.groupingBy(CheckType::getParentId)); // 5. 拼接父 + 子 List result = new ArrayList<>(); @@ -92,34 +94,137 @@ public class CheckTypeController extends BaseController { } // 6. 返回(total 是父节点总数) - Page page = - new Page<>(pageNo, pageSize, parentPage.getTotal()); + Page page = new Page<>(pageNo, pageSize, parentPage.getTotal()); page.setRecords(result); return AjaxResult.success(page); } + /** * 获取检查方法列表 */ - @GetMapping({"/method/list", "/check-method/list"}) + @GetMapping({ "/method/list", "/check-method/list" }) public AjaxResult methodList() { List list = checkMethodService.list(); return AjaxResult.success(list); } - + /** * 获取检查部位列表 */ - @GetMapping({"/part/list", "/check-part/list"}) + @GetMapping({ "/part/list", "/check-part/list" }) public AjaxResult partList() { List list = checkPartService.list(); return AjaxResult.success(list); } + /** + * 根据当前登录用户过滤可见的检查套餐项目(差异化显示) + *

+ * 过滤规则: + * 1. 全院套餐(packageLevel="1"):任何医生可见 + * 2. 科室套餐(packageLevel="2"):仅当前科室医生可见(需传 deptName) + * 3. 个人套餐(packageLevel="3"):仅当前登录用户可见 + *

+ * + * @param deptName 当前登录用户所在科室名称(由前端传入) + * @return 可用套餐列表(含明细检查项目) + */ + @GetMapping({ "/package/filtered", "/check-package/filtered" }) + public AjaxResult getFilteredPackages(@RequestParam(required = false) String deptName) { + // 获取当前登录用户名,用于匹配个人套餐 + String currentUsername; + try { + currentUsername = SecurityUtils.getUsername(); + } catch (Exception e) { + currentUsername = null; + } + + // 查询未停用(isDisabled=0)的全部套餐 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(CheckPackage::getIsDisabled, 0) + .orderByAsc(CheckPackage::getNumber); + List allPackages = checkPackageService.list(wrapper); + + // 按照三级规则筛选出本次登录用户可见的套餐 + final String finalUsername = currentUsername; + final String finalDept = (deptName != null) ? deptName.trim() : ""; + List visiblePackages = allPackages.stream().filter(pkg -> { + String level = pkg.getPackageLevel(); // 1:全院 2:科室 3:个人 + if ("1".equals(level)) { + // 全院套餐 - 所有人可见 + return true; + } else if ("2".equals(level)) { + // 科室套餐 - department 字段包含当前科室 + String dept = pkg.getDepartment(); + return dept != null && !finalDept.isEmpty() && dept.contains(finalDept); + } else if ("3".equals(level)) { + // 个人套餐 - user 字段包含当前账号 + String user = pkg.getUser(); + return user != null && finalUsername != null && user.contains(finalUsername); + } + return false; + }).collect(Collectors.toList()); + + if (visiblePackages.isEmpty()) { + return AjaxResult.success(Collections.emptyList()); + } + + // 批量查明细,拼装返回结构 + List packageIds = visiblePackages.stream().map(CheckPackage::getId).collect(Collectors.toList()); + List details = checkPackageDetailService.list( + new LambdaQueryWrapper().in(CheckPackageDetail::getPackageId, packageIds)); + // 按套餐ID分组明细 + Map> detailMap = details.stream() + .collect(Collectors.groupingBy(CheckPackageDetail::getPackageId)); + + // 构造返回视图:每个套餐是一个分组,下挂若干检查项目 + List> result = visiblePackages.stream().map(pkg -> { + Map group = new LinkedHashMap<>(); + group.put("packageId", pkg.getId()); + group.put("packageName", pkg.getPackageName()); + group.put("packageLevel", pkg.getPackageLevel()); // 1/2/3 + group.put("packageLevelText", parseLevelText(pkg.getPackageLevel())); + group.put("packagePrice", pkg.getPackagePrice()); + group.put("serviceFee", pkg.getServiceFee()); + group.put("packagePriceEnabled", pkg.getPackagePriceEnabled()); + // 明细列表 + List> items = (detailMap.getOrDefault(pkg.getId(), Collections.emptyList())) + .stream().map(d -> { + Map item = new LinkedHashMap<>(); + item.put("id", d.getId()); + item.put("packageId", d.getPackageId()); + item.put("itemCode", d.getItemCode()); + item.put("itemName", d.getItemName()); + item.put("unitPrice", d.getUnitPrice()); // 单价 + item.put("amount", d.getAmount()); // 金额 + item.put("serviceCharge", d.getServiceCharge()); // 服务费 + item.put("quantity", d.getQuantity()); + item.put("orderNum", d.getOrderNum()); + return item; + }).collect(Collectors.toList()); + group.put("items", items); + return group; + }).collect(Collectors.toList()); + + return AjaxResult.success(result); + } + + /** 套餐级别文字映射 */ + private String parseLevelText(String level) { + if ("1".equals(level)) + return "全院"; + if ("2".equals(level)) + return "科室"; + if ("3".equals(level)) + return "个人"; + return "未知"; + } + /** * 获取检查套餐列表(支持分页和筛选) */ - @GetMapping({"/package/list", "/check-package/list"}) + @GetMapping({ "/package/list", "/check-package/list" }) public AjaxResult packageList( @RequestParam(required = false) Integer pageNo, @RequestParam(required = false) Integer pageSize, @@ -131,9 +236,9 @@ public class CheckTypeController extends BaseController { @RequestParam(required = false) String user, @RequestParam(required = false) String startDate, @RequestParam(required = false) String endDate) { - + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - + // 添加筛选条件 if (organization != null && !organization.isEmpty()) { wrapper.eq(CheckPackage::getOrganization, organization); @@ -159,20 +264,20 @@ public class CheckTypeController extends BaseController { if (endDate != null && !endDate.isEmpty()) { wrapper.le(CheckPackage::getMaintainDate, LocalDate.parse(endDate)); } - + // 按更新时间倒序排列 wrapper.orderByDesc(CheckPackage::getUpdateTime); - + // 如果需要分页 if (pageNo != null && pageSize != null) { - com.baomidou.mybatisplus.extension.plugins.pagination.Page page = - new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNo, pageSize); - com.baomidou.mybatisplus.extension.plugins.pagination.Page result = - checkPackageService.page(page, wrapper); + com.baomidou.mybatisplus.extension.plugins.pagination.Page page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>( + pageNo, pageSize); + com.baomidou.mybatisplus.extension.plugins.pagination.Page result = checkPackageService + .page(page, wrapper); return AjaxResult.success(result); } else { List list = checkPackageService.list(wrapper); - return AjaxResult.success(list); + return AjaxResult.success(list); } } @@ -203,7 +308,7 @@ public class CheckTypeController extends BaseController { /** * 根据ID获取检查套餐详情 */ - @GetMapping({"/package/{id}", "/check-package/{id}"}) + @GetMapping({ "/package/{id}", "/check-package/{id}" }) public R getCheckPackageById(@PathVariable Long id) { return checkPackageAppService.getCheckPackageById(id); } @@ -211,7 +316,7 @@ public class CheckTypeController extends BaseController { /** * 新增检查套餐 */ - @PostMapping({"/package", "/check-package"}) + @PostMapping({ "/package", "/check-package" }) public R addCheckPackage(@Valid @RequestBody CheckPackageDto checkPackageDto) { return checkPackageAppService.addCheckPackage(checkPackageDto); } @@ -219,7 +324,7 @@ public class CheckTypeController extends BaseController { /** * 更新检查套餐 */ - @PutMapping({"/package", "/check-package"}) + @PutMapping({ "/package", "/check-package" }) public R updateCheckPackage(@Valid @RequestBody CheckPackageDto checkPackageDto) { return checkPackageAppService.updateCheckPackage(checkPackageDto); } @@ -227,7 +332,7 @@ public class CheckTypeController extends BaseController { /** * 删除检查套餐 */ - @DeleteMapping({"/package/{id}", "/check-package/{id}"}) + @DeleteMapping({ "/package/{id}", "/check-package/{id}" }) public R deleteCheckPackage(@PathVariable Long id) { return checkPackageAppService.deleteCheckPackage(id); } diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/controller/ExamApplyController.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/controller/ExamApplyController.java new file mode 100644 index 00000000..93e95050 --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/controller/ExamApplyController.java @@ -0,0 +1,478 @@ +package com.openhis.web.check.controller; + +import cn.hutool.core.date.DateUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.core.common.core.controller.BaseController; +import com.core.common.core.domain.AjaxResult; +import com.core.common.core.page.TableDataInfo; +import com.core.common.utils.AssignSeqUtil; +import com.core.common.utils.SecurityUtils; +import com.openhis.administration.domain.ChargeItem; +import com.openhis.administration.service.IChargeItemService; +import com.openhis.check.domain.ExamApply; +import com.openhis.check.domain.ExamApplyItem; +import com.openhis.check.service.IExamApplyItemService; +import com.openhis.check.service.IExamApplyService; +import com.openhis.common.constant.CommonConstants; +import com.openhis.common.enums.AssignSeqEnum; +import com.openhis.common.enums.ChargeItemStatus; +import com.openhis.common.enums.GenerateSource; +import com.openhis.common.enums.RequestStatus; +import com.openhis.web.check.dto.ExamApplyDto; +import com.openhis.web.check.dto.ExamApplyItemDto; +import com.openhis.workflow.domain.ServiceRequest; +import com.openhis.workflow.service.IServiceRequestService; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import java.util.Date; +import java.util.List; + +/** + * 检查申请主表 Controller + *

+ * 核心职责:检查申请单的增删查,保存时同步写入门诊医嘱表(wor_service_request) + * 和费用项表(adm_charge_item),删除时级联清理,保证业务数据闭环。 + *

+ */ +@RestController +@RequestMapping("/exam/apply") +public class ExamApplyController extends BaseController { + + @Autowired + private IExamApplyService examApplyService; + + @Autowired + private IExamApplyItemService examApplyItemService; + + @Autowired + private IServiceRequestService serviceRequestService; + + @Autowired + private IChargeItemService chargeItemService; + + @Autowired + private AssignSeqUtil assignSeqUtil; + + /** + * 查询检查申请单列表 + */ + @GetMapping("/list") + public TableDataInfo list(ExamApply examApply) { + startPage(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (examApply.getVisitNo() != null) { + wrapper.eq(ExamApply::getVisitNo, examApply.getVisitNo()); + } + wrapper.orderByDesc(ExamApply::getApplyTime); + List list = examApplyService.list(wrapper); + + // 为每条申请单计算总金额 + for (ExamApply apply : list) { + List items = examApplyItemService.list( + new LambdaQueryWrapper() + .eq(ExamApplyItem::getApplyNo, apply.getApplyNo())); + + BigDecimal totalAmount = BigDecimal.ZERO; + + for (ExamApplyItem item : items) { + if (item.getItemFee() != null) { + totalAmount = totalAmount.add(item.getItemFee()); + } + } + + apply.setTotalAmount(totalAmount); + } + + return getDataTable(list); + } + + /** + * 获取申请单详细信息(包含明细) + */ + @GetMapping(value = "/{applyNo}") + public AjaxResult getInfo(@PathVariable("applyNo") String applyNo) { + ExamApply examApply = examApplyService.getById(applyNo); + if (examApply == null) { + return AjaxResult.error("未找到申请单信息"); + } + AjaxResult ajax = AjaxResult.success(); + ajax.put("data", examApply); + + // 挂载项目明细 + List items = examApplyItemService.list( + new LambdaQueryWrapper().eq(ExamApplyItem::getApplyNo, applyNo)); + ajax.put("items", items); + + return ajax; + } + + /** + * 新增检查申请单 + *

+ * 核心级联保存逻辑: + * 1. 生成申请单号,保存 exam_apply 主表 + * 2. 批量保存 exam_apply_item 明细表 + * 3. 为每条明细写入 wor_service_request(门诊医嘱),使检查进入医嘱体系 + * 4. 为每条医嘱写入 adm_charge_item(费用项),使检查费用进入收费系统 + * 5. 回写 exam_apply_item.service_request_id,建立双向关联 + *

+ */ + @PostMapping + @Transactional(rollbackFor = Exception.class) + public AjaxResult add(@RequestBody @Validated ExamApplyDto dto) { + + // ========== 1. 生成申请单号并保存主表 ========== + String dateStr = DateUtil.format(new Date(), "yyyyMMdd"); + String random4 = String.format("%04d", (int) (Math.random() * 10000)); + String applyNo = "EX" + dateStr + random4; + + ExamApply examApply = new ExamApply(); + BeanUtils.copyProperties(dto, examApply); + examApply.setApplyNo(applyNo); + examApply.setApplyTime(LocalDateTime.now()); + examApply.setCreateTime(new Date()); + examApply.setApplyStatus(0); // 0=已开单 + + // 操作员工号取当前登录用户 + try { + examApply.setOperatorId(SecurityUtils.getUsername()); + } catch (Exception e) { + examApply.setOperatorId("system"); + } + examApplyService.save(examApply); + + // ========== 2. 批量保存明细 + 写入门诊医嘱 + 写入费用项 ========== + if (dto.getItems() != null && !dto.getItems().isEmpty()) { + // 获取当前登录用户信息,用于写入医嘱和费用项 + Long currentUserId = null; + Long currentOrgId = null; + Integer tenantId = null; + String currentUsername = "system"; + try { + currentUserId = SecurityUtils.getLoginUser().getPractitionerId(); + currentOrgId = SecurityUtils.getLoginUser().getOrgId(); + tenantId = SecurityUtils.getLoginUser().getTenantId(); + currentUsername = SecurityUtils.getUsername(); + } catch (Exception e) { + // 获取失败时使用默认值 + } + + Date now = new Date(); + int seq = 1; + + for (ExamApplyItemDto itemDto : dto.getItems()) { + // ----- 2a. 保存检查明细 ----- + ExamApplyItem item = new ExamApplyItem(); + BeanUtils.copyProperties(itemDto, item); + item.setApplyNo(applyNo); + item.setItemSeq(seq++); + item.setItemStatus(0); // 同主表状态:已开单 + examApplyItemService.save(item); + + // ----- 2b. 写入门诊医嘱表 wor_service_request ----- + ServiceRequest serviceRequest = new ServiceRequest(); + // 生成唯一服务编码(SR + 日期 + 4位序列) + serviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4)); + serviceRequest.setStatusEnum(RequestStatus.DRAFT.getValue()); // 草稿/待发送 + serviceRequest.setIntentEnum(1); // 意图:医嘱 + serviceRequest.setQuantity(BigDecimal.ONE); // 检查默认数量1 + serviceRequest.setUnitCode("次"); // 单位 + + // 通过 basedOnTable + basedOnId 关联回检查申请主表 + serviceRequest.setBasedOnTable("exam_apply"); + serviceRequest.setBasedOnId(examApply.getId()); + + // 检查申请不走诊疗定义,设置为0占位(数据库有NOT NULL约束) + serviceRequest.setActivityId(0L); + + // 患者和就诊信息 —— 使用前端传递的数字型ID + if (dto.getPatientIdNum() != null) { + serviceRequest.setPatientId(dto.getPatientIdNum()); + } + if (dto.getEncounterId() != null) { + serviceRequest.setEncounterId(dto.getEncounterId()); + } + + serviceRequest.setRequesterId(currentUserId); // 开单医生 + serviceRequest.setOrgId(currentOrgId); // 执行科室 + serviceRequest.setAuthoredTime(now); // 签发时间 + serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 来源=医生开立 + + // 将项目名称存入 contentJson,使医嘱列表能通过 JSON 字段回显 adviceName + if (itemDto.getItemName() != null) { + serviceRequest + .setContentJson("{\"adviceName\":\"" + itemDto.getItemName().replace("\"", "\\\"") + "\"}"); + } + + // 租户和审计字段 + if (tenantId != null) { + serviceRequest.setTenantId(tenantId); + } + serviceRequest.setCreateBy(currentUsername); + serviceRequest.setCreateTime(now); + + serviceRequestService.save(serviceRequest); + + // ----- 2c. 写入费用项表 adm_charge_item ----- + ChargeItem chargeItem = new ChargeItem(); + chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix() + .concat(serviceRequest.getBusNo())); // 费用项编码 = CI + 服务编码 + chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 草稿状态,待收费 + chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); + + // 关联医嘱表 + chargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST); + chargeItem.setServiceId(serviceRequest.getId()); + + // 患者和就诊 + if (dto.getPatientIdNum() != null) { + chargeItem.setPatientId(dto.getPatientIdNum()); + } + if (dto.getEncounterId() != null) { + chargeItem.setEncounterId(dto.getEncounterId()); + } + + chargeItem.setEntererId(currentUserId); // 开立人 + chargeItem.setRequestingOrgId(currentOrgId); // 开立科室 + chargeItem.setEnteredDate(now); // 开立时间 + + // 以下字段均有 NOT NULL 约束,检查申请不走定价/账户体系,用0占位 + chargeItem.setDefinitionId(0L); // 费用定价ID + chargeItem.setAccountId(0L); // 关联账户ID + chargeItem.setContextEnum(2); // 类型:2=诊疗 + chargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION); // 产品来源表 + chargeItem.setProductId(0L); // 产品ID + + // 金额:单价和总价取检查项目费用 + BigDecimal fee = itemDto.getItemFee() != null ? itemDto.getItemFee() : BigDecimal.ZERO; + chargeItem.setQuantityValue(BigDecimal.ONE); // 数量 + chargeItem.setQuantityUnit("次"); // 单位 + chargeItem.setUnitPrice(fee); // 单价 + chargeItem.setTotalPrice(fee); // 总价 = 单价 × 1 + + // 租户和审计字段 + if (tenantId != null) { + chargeItem.setTenantId(tenantId); + } + chargeItem.setCreateBy(currentUsername); + chargeItem.setCreateTime(now); + + chargeItemService.save(chargeItem); + + // ----- 2d. 回写明细表的医嘱关联ID ----- + item.setServiceRequestId(serviceRequest.getId()); + examApplyItemService.updateById(item); + } + } + + return AjaxResult.success("开单成功", applyNo); + } + + /** + * 修改检查申请单 + *

+ * 采用「先删旧关联再重建」策略: + * 1. 更新 exam_apply 主表基本字段 + * 2. 删除旧的明细、医嘱、费用项 + * 3. 用新提交的明细重新创建医嘱和费用项 + *

+ */ + @PutMapping + @Transactional(rollbackFor = Exception.class) + public AjaxResult edit(@RequestBody @Validated ExamApplyDto dto) { + String applyNo = dto.getApplyNo(); + if (applyNo == null || applyNo.isEmpty()) { + return AjaxResult.error("修改时申请单号不能为空"); + } + + // ========== 1. 更新主表基本信息 ========== + ExamApply examApply = examApplyService.getById(applyNo); + if (examApply == null) { + return AjaxResult.error("未找到该申请单"); + } + // 只更新可编辑字段,不覆盖系统字段(applyNo, createTime等) + examApply.setNatureofCost(dto.getNatureofCost()); + examApply.setApplyDeptCode(dto.getApplyDeptCode()); + examApply.setPerformDeptCode(dto.getPerformDeptCode()); + examApply.setApplyDocCode(dto.getApplyDocCode()); + examApply.setClinicDesc(dto.getClinicDesc()); + examApply.setContraindication(dto.getContraindication()); + examApply.setClinicalDiag(dto.getClinicalDiag()); + examApply.setMedicalHistorySummary(dto.getMedicalHistorySummary()); + examApply.setPurposeDesc(dto.getPurposeDesc()); + examApply.setPurposeofInspection(dto.getPurposeofInspection()); + examApply.setInspectionArea(dto.getInspectionArea()); + examApply.setInspectionMethod(dto.getInspectionMethod()); + examApply.setApplyRemark(dto.getApplyRemark()); + examApply.setIsUrgent(dto.getIsUrgent()); + examApplyService.updateById(examApply); + + // ========== 2. 删除旧的明细、医嘱、费用项 ========== + List oldItems = examApplyItemService.list( + new LambdaQueryWrapper().eq(ExamApplyItem::getApplyNo, applyNo)); + for (ExamApplyItem oldItem : oldItems) { + if (oldItem.getServiceRequestId() != null) { + // 删除旧费用项 + chargeItemService.deleteByServiceTableAndId( + CommonConstants.TableName.WOR_SERVICE_REQUEST, + oldItem.getServiceRequestId()); + // 删除旧医嘱 + serviceRequestService.removeById(oldItem.getServiceRequestId()); + } + } + // 删除旧明细 + examApplyItemService.remove( + new LambdaQueryWrapper().eq(ExamApplyItem::getApplyNo, applyNo)); + + // ========== 3. 用新提交的明细重新创建 ========== + if (dto.getItems() != null && !dto.getItems().isEmpty()) { + Long currentUserId = null; + Long currentOrgId = null; + Integer tenantId = null; + String currentUsername = "system"; + try { + currentUserId = SecurityUtils.getLoginUser().getPractitionerId(); + currentOrgId = SecurityUtils.getLoginUser().getOrgId(); + tenantId = SecurityUtils.getLoginUser().getTenantId(); + currentUsername = SecurityUtils.getUsername(); + } catch (Exception e) { + // 获取失败时使用默认值 + } + + Date now = new Date(); + int seq = 1; + + for (ExamApplyItemDto itemDto : dto.getItems()) { + // 保存新明细 + ExamApplyItem item = new ExamApplyItem(); + BeanUtils.copyProperties(itemDto, item); + item.setApplyNo(applyNo); + item.setItemSeq(seq++); + item.setItemStatus(0); + examApplyItemService.save(item); + + // 写入门诊医嘱 + ServiceRequest serviceRequest = new ServiceRequest(); + serviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4)); + serviceRequest.setStatusEnum(RequestStatus.DRAFT.getValue()); + serviceRequest.setIntentEnum(1); + serviceRequest.setQuantity(BigDecimal.ONE); + serviceRequest.setUnitCode("次"); + serviceRequest.setBasedOnTable("exam_apply"); + serviceRequest.setBasedOnId(examApply.getId()); + serviceRequest.setActivityId(0L); + + if (dto.getPatientIdNum() != null) { + serviceRequest.setPatientId(dto.getPatientIdNum()); + } + if (dto.getEncounterId() != null) { + serviceRequest.setEncounterId(dto.getEncounterId()); + } + serviceRequest.setRequesterId(currentUserId); + serviceRequest.setOrgId(currentOrgId); + serviceRequest.setAuthoredTime(now); + serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); + + // 将项目名称存入 contentJson,使医嘱列表能通过 JSON 字段回显 adviceName + if (itemDto.getItemName() != null) { + serviceRequest + .setContentJson("{\"adviceName\":\"" + itemDto.getItemName().replace("\"", "\\\"") + "\"}"); + } + + if (tenantId != null) { + serviceRequest.setTenantId(tenantId); + } + serviceRequest.setCreateBy(currentUsername); + serviceRequest.setCreateTime(now); + serviceRequestService.save(serviceRequest); + + // 写入费用项 + ChargeItem chargeItem = new ChargeItem(); + chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix() + .concat(serviceRequest.getBusNo())); + chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); + chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); + chargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST); + chargeItem.setServiceId(serviceRequest.getId()); + + if (dto.getPatientIdNum() != null) { + chargeItem.setPatientId(dto.getPatientIdNum()); + } + if (dto.getEncounterId() != null) { + chargeItem.setEncounterId(dto.getEncounterId()); + } + chargeItem.setEntererId(currentUserId); + chargeItem.setRequestingOrgId(currentOrgId); + chargeItem.setEnteredDate(now); + chargeItem.setDefinitionId(0L); + chargeItem.setAccountId(0L); + chargeItem.setContextEnum(2); + chargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION); + chargeItem.setProductId(0L); + + BigDecimal fee = itemDto.getItemFee() != null ? itemDto.getItemFee() : BigDecimal.ZERO; + chargeItem.setQuantityValue(BigDecimal.ONE); + chargeItem.setQuantityUnit("次"); + chargeItem.setUnitPrice(fee); + chargeItem.setTotalPrice(fee); + + if (tenantId != null) { + chargeItem.setTenantId(tenantId); + } + chargeItem.setCreateBy(currentUsername); + chargeItem.setCreateTime(now); + chargeItemService.save(chargeItem); + + // 回写明细表的医嘱关联ID + item.setServiceRequestId(serviceRequest.getId()); + examApplyItemService.updateById(item); + } + } + + return AjaxResult.success("修改成功", applyNo); + } + + /** + * 删除检查申请单 + *

+ * 级联操作: + * 1. 查询并删除关联的门诊医嘱(wor_service_request)和费用项(adm_charge_item) + * 2. 删除检查明细(exam_apply_item) + * 3. 删除主表(exam_apply) + *

+ */ + @DeleteMapping("/{applyNo}") + @Transactional(rollbackFor = Exception.class) + public AjaxResult remove(@PathVariable String applyNo) { + // 1. 查询该申请单的所有明细,获取关联的医嘱ID + List items = examApplyItemService.list( + new LambdaQueryWrapper().eq(ExamApplyItem::getApplyNo, applyNo)); + + for (ExamApplyItem item : items) { + if (item.getServiceRequestId() != null) { + // 删除费用项(按医嘱表名+医嘱ID定位) + chargeItemService.deleteByServiceTableAndId( + CommonConstants.TableName.WOR_SERVICE_REQUEST, + item.getServiceRequestId()); + // 删除门诊医嘱 + serviceRequestService.removeById(item.getServiceRequestId()); + } + } + + // 2. 删除明细表 + examApplyItemService.remove( + new LambdaQueryWrapper().eq(ExamApplyItem::getApplyNo, applyNo)); + + // 3. 删除主表 + examApplyService.removeById(applyNo); + + return AjaxResult.success("删除/作废成功"); + } +} diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/dto/ExamApplyDto.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/dto/ExamApplyDto.java new file mode 100644 index 00000000..c6390dc7 --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/dto/ExamApplyDto.java @@ -0,0 +1,97 @@ +package com.openhis.web.check.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 检查申请数据传输对象 + */ +@Data +public class ExamApplyDto implements Serializable { + private static final long serialVersionUID = 1L; + + /** 申请单号 (EXYYYYMMDD####) */ + private String applyNo; + + /** 患者主索引 EMPI */ + private String patientId; + + /** 门诊就诊流水号 */ + private String visitNo; + + /** 申请科室代码 */ + private String applyDeptCode; + + /** 执行科室代码 */ + private String performDeptCode; + + /** 申请医生工号 */ + private String applyDocCode; + + /** 申请时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime applyTime; + + /** 就诊卡号 */ + private String medicalrecordNumber; + + /** 费用性质 */ + private String natureofCost; + + /** 诊断描述 */ + private String clinicDesc; + + /** 禁忌症 */ + private String contraindication; + + /** 病史摘要 */ + private String medicalHistorySummary; + + /** 体格检查 */ + private String purposeofInspection; + + /** 申检部位 */ + private String inspectionArea; + + /** 检查方法 */ + private String inspectionMethod; + + /** 备注 */ + private String applyRemark; + + /** 检查大类代码 */ + private String examTypeCode; + + /** 临床诊断 */ + private String clinicalDiag; + + /** 检查目的 */ + private String purposeDesc; + + /** 加急标志 0 普通 1 加急 */ + private Integer isUrgent; + + /** 妊娠状态 0未知 1未孕 2可能孕 3孕妇 */ + private Integer pregnancyState; + + /** 过敏史 */ + private String allergyDesc; + + /** 申请单状态 0已开单 1已收费 2已预约 3已签到 4部分报告 5已完告 6作废 */ + private Integer applyStatus; + + /** 就诊ID(用于写入门诊医嘱和费用项关联) */ + private Long encounterId; + + /** 患者数字ID(用于写入门诊医嘱和费用项,与 patientId 的 EMPI 字符串不同) */ + private Long patientIdNum; + + /** + * 绑定的明细项目列表 + */ + private List items; +} diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/dto/ExamApplyItemDto.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/dto/ExamApplyItemDto.java new file mode 100644 index 00000000..a063740b --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/dto/ExamApplyItemDto.java @@ -0,0 +1,50 @@ +package com.openhis.web.check.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 检查申请明细项目数据传输对象 + */ +@Data +public class ExamApplyItemDto implements Serializable { + private static final long serialVersionUID = 1L; + + /** 项目代码 (院内收费项目) */ + private String itemCode; + + /** 项目名称 */ + private String itemName; + + /** 国家项目代码 */ + private String nationalItemCode; + + /** 检查部位代码 */ + private String bodyPartCode; + + /** 检查方法代码 */ + private String examMethodCode; + + /** 对比剂药品 */ + private String contrastDrug; + + /** 对比剂剂量 */ + private String contrastDose; + + /** 执行科室代码 */ + private String performDeptCode; + + /** 预约号 */ + private String appointmentNo; + + /** 项目单价 */ + private BigDecimal itemFee; + + /** 行状态 0 已开单 1 已收费 ... */ + private Integer itemStatus; + + /** 检查备注 */ + private String remark; +} diff --git a/openhis-server-new/openhis-application/src/main/resources/mapper/doctorstation/DoctorStationAdviceAppMapper.xml b/openhis-server-new/openhis-application/src/main/resources/mapper/doctorstation/DoctorStationAdviceAppMapper.xml index 6b7819fc..bf8fa65f 100644 --- a/openhis-server-new/openhis-application/src/main/resources/mapper/doctorstation/DoctorStationAdviceAppMapper.xml +++ b/openhis-server-new/openhis-application/src/main/resources/mapper/doctorstation/DoctorStationAdviceAppMapper.xml @@ -547,7 +547,7 @@ null AS skin_test_flag, null AS inject_flag, null AS group_id, - T2.NAME AS advice_name, + COALESCE(T2.NAME, CASE WHEN T1.content_json IS NOT NULL AND T1.content_json != '' THEN T1.content_json::json->>'adviceName' ELSE NULL END) AS advice_name, '' AS volume, '' AS lot_number, T1.quantity AS quantity, diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/domain/ExamApply.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/domain/ExamApply.java new file mode 100644 index 00000000..120d2807 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/domain/ExamApply.java @@ -0,0 +1,127 @@ +package com.openhis.check.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.core.common.core.domain.HisBaseEntity; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 检查申请主表 + *

+ * 继承 {@link HisBaseEntity} 获得: + *

    + *
  • createBy / createTime(INSERT 自动填充)
  • + *
  • updateBy / updateTime(UPDATE 自动填充)
  • + *
  • tenantId(INSERT 自动填充)
  • + *
  • deleteFlag + {@code @TableLogic} 逻辑删除(0=未删,1=已删)
  • + *
+ */ +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@TableName(value = "exam_apply", autoResultMap = true) +public class ExamApply extends HisBaseEntity { + + /** 数据库自增ID,用于与门诊医嘱表(wor_service_request.based_on_id)关联 */ + private Long id; + + /** 申请单号 (EXYYYYMMDD####),业务主键 */ + @TableId(type = IdType.INPUT) + private String applyNo; + + /** 患者主索引 EMPI */ + private String patientId; + + /** 门诊就诊流水号 */ + private String visitNo; + + /** 申请科室代码 */ + private String applyDeptCode; + + /** 执行科室代码 */ + private String performDeptCode; + + /** 申请医生工号 */ + private String applyDocCode; + + /** 申请时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime applyTime; + + /** 就诊卡号 */ + private String medicalrecordNumber; + + /** 费用性质(自费医疗/医保报销) */ + private String natureofCost; + + /** 诊断描述 */ + private String clinicDesc; + + /** 禁忌症 */ + private String contraindication; + + /** 病史摘要 */ + private String medicalHistorySummary; + + /** 体格检查 */ + private String purposeofInspection; + + /** 申检部位 */ + private String inspectionArea; + + /** 检查方法 */ + private String inspectionMethod; + + /** 备注 */ + private String applyRemark; + + /** 检查大类代码(如 CT / ECG / GI) */ + private String examTypeCode; + + /** 临床诊断 */ + private String clinicalDiag; + + /** 检查目的 */ + private String purposeDesc; + + /** 加急标志 0=普通 1=加急 */ + private Integer isUrgent; + + /** 妊娠状态 0=未知 1=未孕 2=可能孕 3=孕妇 */ + private Integer pregnancyState; + + /** 过敏史 */ + private String allergyDesc; + + /** + * 申请单状态 + * 0=已开单 1=已收费 2=已预约 3=已签到 4=部分报告 5=已完告 6=作废 + */ + private Integer applyStatus; + + /** 是否已收费 0=否 1=是 */ + private Integer isCharged; + + /** 是否已退费 0=否 1=是 */ + private Integer isRefunded; + + /** 是否已执行 0=否 1=是 */ + private Integer isExecuted; + + /** 操作员工号(向后兼容保留) */ + private String operatorId; + + /** + * 总金额(非数据库字段,由明细项目计算得出) + */ + @TableField(exist = false) + private BigDecimal totalAmount; +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/domain/ExamApplyItem.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/domain/ExamApplyItem.java new file mode 100644 index 00000000..e13b8d77 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/domain/ExamApplyItem.java @@ -0,0 +1,69 @@ +package com.openhis.check.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 检查申请明细表 + */ +@Data +@Accessors(chain = true) +@TableName(value = "exam_apply_item", autoResultMap = true) +public class ExamApplyItem implements Serializable { + private static final long serialVersionUID = 1L; + + /** 明细ID */ + @TableId(type = IdType.AUTO) + private Long itemId; + + /** 申请单号 */ + private String applyNo; + + /** 项目序号 */ + private Integer itemSeq; + + /** 项目代码 (院内收费项目) */ + private String itemCode; + + /** 项目名称 */ + private String itemName; + + /** 国家项目代码 */ + private String nationalItemCode; + + /** 检查部位代码 */ + private String bodyPartCode; + + /** 检查方法代码 */ + private String examMethodCode; + + /** 对比剂药品 */ + private String contrastDrug; + + /** 对比剂剂量 */ + private String contrastDose; + + /** 执行科室代码 */ + private String performDeptCode; + + /** 预约号 */ + private String appointmentNo; + + /** 项目单价 */ + private BigDecimal itemFee; + + /** 行状态 0 已开单 1 已收费 ... */ + private Integer itemStatus; + + /** 检查备注 */ + private String remark; + + /** 关联的门诊医嘱ID (wor_service_request.id) */ + private Long serviceRequestId; +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/mapper/ExamApplyItemMapper.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/mapper/ExamApplyItemMapper.java new file mode 100644 index 00000000..2a6a64c2 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/mapper/ExamApplyItemMapper.java @@ -0,0 +1,10 @@ +package com.openhis.check.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.openhis.check.domain.ExamApplyItem; + +/** + * 检查申请明细表 Mapper 接口 + */ +public interface ExamApplyItemMapper extends BaseMapper { +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/mapper/ExamApplyMapper.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/mapper/ExamApplyMapper.java new file mode 100644 index 00000000..5f1e8863 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/mapper/ExamApplyMapper.java @@ -0,0 +1,10 @@ +package com.openhis.check.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.openhis.check.domain.ExamApply; + +/** + * 检查申请主表 Mapper 接口 + */ +public interface ExamApplyMapper extends BaseMapper { +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/service/IExamApplyItemService.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/service/IExamApplyItemService.java new file mode 100644 index 00000000..57f194b4 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/service/IExamApplyItemService.java @@ -0,0 +1,10 @@ +package com.openhis.check.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.openhis.check.domain.ExamApplyItem; + +/** + * 检查申请明细表 服务层接口 + */ +public interface IExamApplyItemService extends IService { +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/service/IExamApplyService.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/service/IExamApplyService.java new file mode 100644 index 00000000..156639a4 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/service/IExamApplyService.java @@ -0,0 +1,10 @@ +package com.openhis.check.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.openhis.check.domain.ExamApply; + +/** + * 检查申请主表 服务层接口 + */ +public interface IExamApplyService extends IService { +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/service/impl/ExamApplyItemServiceImpl.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/service/impl/ExamApplyItemServiceImpl.java new file mode 100644 index 00000000..5aea71db --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/service/impl/ExamApplyItemServiceImpl.java @@ -0,0 +1,15 @@ +package com.openhis.check.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.openhis.check.domain.ExamApplyItem; +import com.openhis.check.mapper.ExamApplyItemMapper; +import com.openhis.check.service.IExamApplyItemService; +import org.springframework.stereotype.Service; + +/** + * 检查申请明细表 服务层实现类 + */ +@Service +public class ExamApplyItemServiceImpl extends ServiceImpl + implements IExamApplyItemService { +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/service/impl/ExamApplyServiceImpl.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/service/impl/ExamApplyServiceImpl.java new file mode 100644 index 00000000..76db42ca --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/check/service/impl/ExamApplyServiceImpl.java @@ -0,0 +1,14 @@ +package com.openhis.check.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.openhis.check.domain.ExamApply; +import com.openhis.check.mapper.ExamApplyMapper; +import com.openhis.check.service.IExamApplyService; +import org.springframework.stereotype.Service; + +/** + * 检查申请主表 服务层实现类 + */ +@Service +public class ExamApplyServiceImpl extends ServiceImpl implements IExamApplyService { +} diff --git a/openhis-ui-vue3/src/views/doctorstation/components/examination/examinationApplication.vue b/openhis-ui-vue3/src/views/doctorstation/components/examination/examinationApplication.vue new file mode 100644 index 00000000..fec11bc7 --- /dev/null +++ b/openhis-ui-vue3/src/views/doctorstation/components/examination/examinationApplication.vue @@ -0,0 +1,837 @@ + + + + + diff --git a/openhis-ui-vue3/src/views/doctorstation/index.vue b/openhis-ui-vue3/src/views/doctorstation/index.vue index c36e4d87..a5519d6f 100644 --- a/openhis-ui-vue3/src/views/doctorstation/index.vue +++ b/openhis-ui-vue3/src/views/doctorstation/index.vue @@ -161,6 +161,10 @@ + + + @@ -215,6 +219,7 @@ import eprescriptionlist from './components/eprescriptionlist.vue'; import HospitalizationDialog from './components/hospitalizationDialog.vue'; import tcmAdvice from './components/tcm/tcmAdvice.vue'; import inspectionApplication from './components/inspection/inspectionApplication.vue'; +import examinationApplication from './components/examination/examinationApplication.vue'; import surgeryApplication from './components/surgery/surgeryApplication.vue'; import DoctorCallDialog from './components/callQueue/DoctorCallDialog.vue'; import { formatDate, formatDateStr } from '@/utils/index'; @@ -306,6 +311,7 @@ const patientDrawerRef = ref(); const prescriptionRef = ref(); const tcmRef = ref(); const inspectionRef = ref(); +const examinationRef = ref(); const surgeryRef = ref(); const emrRef = ref(); const diagnosisRef = ref(); @@ -496,6 +502,11 @@ function handleClick(tab) { inspectionRef.value.getList(); } break; + case 'examination': + if (patientInfo.value && patientInfo.value.encounterId) { + examinationRef.value.getList(); + } + break; case 'surgery': surgeryRef.value.getList(); break; @@ -607,6 +618,7 @@ function handleCardClick(item, index) { prescriptionRef.value.getListInfo(); tcmRef.value.getListInfo(); inspectionRef.value.getList(); + if(examinationRef.value) examinationRef.value.getList(); surgeryRef.value.getList(); diagnosisRef.value.getList(); eprescriptionRef.value.getList();