80 门诊医生站检查申请单开单界面

This commit is contained in:
HuangXinQuan
2026-03-03 16:16:52 +08:00
parent 8810c678c9
commit 9525b1d927
15 changed files with 1872 additions and 28 deletions

View File

@@ -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<CheckType> parentPage = checkTypeService.page(
new Page<>(pageNo, pageSize),
new QueryWrapper<CheckType>()
.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<CheckType> children = checkTypeService.list(
new QueryWrapper<CheckType>().in("parent_id", parentIds)
);
new QueryWrapper<CheckType>().in("parent_id", parentIds));
// 4. 分组
Map<Long, List<CheckType>> childMap =
children.stream().collect(Collectors.groupingBy(CheckType::getParentId));
Map<Long, List<CheckType>> childMap = children.stream().collect(Collectors.groupingBy(CheckType::getParentId));
// 5. 拼接父 + 子
List<CheckType> result = new ArrayList<>();
@@ -92,34 +94,137 @@ public class CheckTypeController extends BaseController {
}
// 6. 返回total 是父节点总数)
Page<CheckType> page =
new Page<>(pageNo, pageSize, parentPage.getTotal());
Page<CheckType> 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<CheckMethod> list = checkMethodService.list();
return AjaxResult.success(list);
}
/**
* 获取检查部位列表
*/
@GetMapping({"/part/list", "/check-part/list"})
@GetMapping({ "/part/list", "/check-part/list" })
public AjaxResult partList() {
List<CheckPart> list = checkPartService.list();
return AjaxResult.success(list);
}
/**
* 根据当前登录用户过滤可见的检查套餐项目(差异化显示)
* <p>
* 过滤规则:
* 1. 全院套餐packageLevel="1"):任何医生可见
* 2. 科室套餐packageLevel="2"):仅当前科室医生可见(需传 deptName
* 3. 个人套餐packageLevel="3"):仅当前登录用户可见
* </p>
*
* @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<CheckPackage> wrapper = new LambdaQueryWrapper<CheckPackage>()
.eq(CheckPackage::getIsDisabled, 0)
.orderByAsc(CheckPackage::getNumber);
List<CheckPackage> allPackages = checkPackageService.list(wrapper);
// 按照三级规则筛选出本次登录用户可见的套餐
final String finalUsername = currentUsername;
final String finalDept = (deptName != null) ? deptName.trim() : "";
List<CheckPackage> 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<Long> packageIds = visiblePackages.stream().map(CheckPackage::getId).collect(Collectors.toList());
List<CheckPackageDetail> details = checkPackageDetailService.list(
new LambdaQueryWrapper<CheckPackageDetail>().in(CheckPackageDetail::getPackageId, packageIds));
// 按套餐ID分组明细
Map<Long, List<CheckPackageDetail>> detailMap = details.stream()
.collect(Collectors.groupingBy(CheckPackageDetail::getPackageId));
// 构造返回视图:每个套餐是一个分组,下挂若干检查项目
List<Map<String, Object>> result = visiblePackages.stream().map(pkg -> {
Map<String, Object> 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<Map<String, Object>> items = (detailMap.getOrDefault(pkg.getId(), Collections.emptyList()))
.stream().map(d -> {
Map<String, Object> 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<CheckPackage> 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<CheckPackage> page =
new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNo, pageSize);
com.baomidou.mybatisplus.extension.plugins.pagination.Page<CheckPackage> result =
checkPackageService.page(page, wrapper);
com.baomidou.mybatisplus.extension.plugins.pagination.Page<CheckPackage> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(
pageNo, pageSize);
com.baomidou.mybatisplus.extension.plugins.pagination.Page<CheckPackage> result = checkPackageService
.page(page, wrapper);
return AjaxResult.success(result);
} else {
List<CheckPackage> 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);
}

View File

@@ -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
* <p>
* 核心职责:检查申请单的增删查,保存时同步写入门诊医嘱表(wor_service_request)
* 和费用项表(adm_charge_item),删除时级联清理,保证业务数据闭环。
* </p>
*/
@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<ExamApply> wrapper = new LambdaQueryWrapper<>();
if (examApply.getVisitNo() != null) {
wrapper.eq(ExamApply::getVisitNo, examApply.getVisitNo());
}
wrapper.orderByDesc(ExamApply::getApplyTime);
List<ExamApply> list = examApplyService.list(wrapper);
// 为每条申请单计算总金额
for (ExamApply apply : list) {
List<ExamApplyItem> items = examApplyItemService.list(
new LambdaQueryWrapper<ExamApplyItem>()
.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<ExamApplyItem> items = examApplyItemService.list(
new LambdaQueryWrapper<ExamApplyItem>().eq(ExamApplyItem::getApplyNo, applyNo));
ajax.put("items", items);
return ajax;
}
/**
* 新增检查申请单
* <p>
* 核心级联保存逻辑:
* 1. 生成申请单号,保存 exam_apply 主表
* 2. 批量保存 exam_apply_item 明细表
* 3. 为每条明细写入 wor_service_request门诊医嘱使检查进入医嘱体系
* 4. 为每条医嘱写入 adm_charge_item费用项使检查费用进入收费系统
* 5. 回写 exam_apply_item.service_request_id建立双向关联
* </p>
*/
@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);
}
/**
* 修改检查申请单
* <p>
* 采用「先删旧关联再重建」策略:
* 1. 更新 exam_apply 主表基本字段
* 2. 删除旧的明细、医嘱、费用项
* 3. 用新提交的明细重新创建医嘱和费用项
* </p>
*/
@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<ExamApplyItem> oldItems = examApplyItemService.list(
new LambdaQueryWrapper<ExamApplyItem>().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<ExamApplyItem>().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);
}
/**
* 删除检查申请单
* <p>
* 级联操作:
* 1. 查询并删除关联的门诊医嘱(wor_service_request)和费用项(adm_charge_item)
* 2. 删除检查明细(exam_apply_item)
* 3. 删除主表(exam_apply)
* </p>
*/
@DeleteMapping("/{applyNo}")
@Transactional(rollbackFor = Exception.class)
public AjaxResult remove(@PathVariable String applyNo) {
// 1. 查询该申请单的所有明细获取关联的医嘱ID
List<ExamApplyItem> items = examApplyItemService.list(
new LambdaQueryWrapper<ExamApplyItem>().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<ExamApplyItem>().eq(ExamApplyItem::getApplyNo, applyNo));
// 3. 删除主表
examApplyService.removeById(applyNo);
return AjaxResult.success("删除/作废成功");
}
}

View File

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

View File

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

View File

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

View File

@@ -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;
/**
* 检查申请主表
* <p>
* 继承 {@link HisBaseEntity} 获得:
* <ul>
* <li>createBy / createTimeINSERT 自动填充)</li>
* <li>updateBy / updateTimeUPDATE 自动填充)</li>
* <li>tenantIdINSERT 自动填充)</li>
* <li>deleteFlag + {@code @TableLogic} 逻辑删除0=未删1=已删)</li>
* </ul>
*/
@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;
}

View File

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

View File

@@ -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<ExamApplyItem> {
}

View File

@@ -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<ExamApply> {
}

View File

@@ -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<ExamApplyItem> {
}

View File

@@ -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<ExamApply> {
}

View File

@@ -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<ExamApplyItemMapper, ExamApplyItem>
implements IExamApplyItemService {
}

View File

@@ -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<ExamApplyMapper, ExamApply> implements IExamApplyService {
}