80 门诊医生站检查申请单开单界面
This commit is contained in:
@@ -6,11 +6,14 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|||||||
import com.core.common.core.controller.BaseController;
|
import com.core.common.core.controller.BaseController;
|
||||||
import com.core.common.core.domain.AjaxResult;
|
import com.core.common.core.domain.AjaxResult;
|
||||||
import com.core.common.core.domain.R;
|
import com.core.common.core.domain.R;
|
||||||
|
import com.core.common.utils.SecurityUtils;
|
||||||
import com.openhis.check.domain.CheckMethod;
|
import com.openhis.check.domain.CheckMethod;
|
||||||
import com.openhis.check.domain.CheckPackage;
|
import com.openhis.check.domain.CheckPackage;
|
||||||
|
import com.openhis.check.domain.CheckPackageDetail;
|
||||||
import com.openhis.check.domain.CheckPart;
|
import com.openhis.check.domain.CheckPart;
|
||||||
import com.openhis.check.domain.CheckType;
|
import com.openhis.check.domain.CheckType;
|
||||||
import com.openhis.check.service.ICheckMethodService;
|
import com.openhis.check.service.ICheckMethodService;
|
||||||
|
import com.openhis.check.service.ICheckPackageDetailService;
|
||||||
import com.openhis.check.service.ICheckPackageService;
|
import com.openhis.check.service.ICheckPackageService;
|
||||||
import com.openhis.check.service.ICheckPartService;
|
import com.openhis.check.service.ICheckPartService;
|
||||||
import com.openhis.check.service.ICheckTypeService;
|
import com.openhis.check.service.ICheckTypeService;
|
||||||
@@ -33,7 +36,7 @@ import java.util.stream.Collectors;
|
|||||||
* @updated 2025-11-26 - 增加套餐设置相关接口
|
* @updated 2025-11-26 - 增加套餐设置相关接口
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping({"/system/check-type", "/system"})
|
@RequestMapping({ "/system/check-type", "/system" })
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class CheckTypeController extends BaseController {
|
public class CheckTypeController extends BaseController {
|
||||||
@@ -42,6 +45,7 @@ public class CheckTypeController extends BaseController {
|
|||||||
private final ICheckMethodService checkMethodService;
|
private final ICheckMethodService checkMethodService;
|
||||||
private final ICheckPartService checkPartService;
|
private final ICheckPartService checkPartService;
|
||||||
private final ICheckPackageService checkPackageService;
|
private final ICheckPackageService checkPackageService;
|
||||||
|
private final ICheckPackageDetailService checkPackageDetailService;
|
||||||
private final ICheckPackageAppService checkPackageAppService;
|
private final ICheckPackageAppService checkPackageAppService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,15 +56,15 @@ public class CheckTypeController extends BaseController {
|
|||||||
@RequestParam(defaultValue = "1") Integer pageNo,
|
@RequestParam(defaultValue = "1") Integer pageNo,
|
||||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||||
|
|
||||||
if (pageSize > 10) pageSize = 10;
|
if (pageSize > 10)
|
||||||
|
pageSize = 10;
|
||||||
|
|
||||||
// 1. 分页查询父节点(NULL + 0 都算父)
|
// 1. 分页查询父节点(NULL + 0 都算父)
|
||||||
Page<CheckType> parentPage = checkTypeService.page(
|
Page<CheckType> parentPage = checkTypeService.page(
|
||||||
new Page<>(pageNo, pageSize),
|
new Page<>(pageNo, pageSize),
|
||||||
new QueryWrapper<CheckType>()
|
new QueryWrapper<CheckType>()
|
||||||
.and(w -> w.isNull("parent_id").or().eq("parent_id", 0))
|
.and(w -> w.isNull("parent_id").or().eq("parent_id", 0))
|
||||||
.orderByAsc("id")
|
.orderByAsc("id"));
|
||||||
);
|
|
||||||
|
|
||||||
if (parentPage.getRecords().isEmpty()) {
|
if (parentPage.getRecords().isEmpty()) {
|
||||||
return AjaxResult.success(parentPage);
|
return AjaxResult.success(parentPage);
|
||||||
@@ -74,12 +78,10 @@ public class CheckTypeController extends BaseController {
|
|||||||
|
|
||||||
// 3. 查询子节点
|
// 3. 查询子节点
|
||||||
List<CheckType> children = checkTypeService.list(
|
List<CheckType> children = checkTypeService.list(
|
||||||
new QueryWrapper<CheckType>().in("parent_id", parentIds)
|
new QueryWrapper<CheckType>().in("parent_id", parentIds));
|
||||||
);
|
|
||||||
|
|
||||||
// 4. 分组
|
// 4. 分组
|
||||||
Map<Long, List<CheckType>> childMap =
|
Map<Long, List<CheckType>> childMap = children.stream().collect(Collectors.groupingBy(CheckType::getParentId));
|
||||||
children.stream().collect(Collectors.groupingBy(CheckType::getParentId));
|
|
||||||
|
|
||||||
// 5. 拼接父 + 子
|
// 5. 拼接父 + 子
|
||||||
List<CheckType> result = new ArrayList<>();
|
List<CheckType> result = new ArrayList<>();
|
||||||
@@ -92,34 +94,137 @@ public class CheckTypeController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 6. 返回(total 是父节点总数)
|
// 6. 返回(total 是父节点总数)
|
||||||
Page<CheckType> page =
|
Page<CheckType> page = new Page<>(pageNo, pageSize, parentPage.getTotal());
|
||||||
new Page<>(pageNo, pageSize, parentPage.getTotal());
|
|
||||||
page.setRecords(result);
|
page.setRecords(result);
|
||||||
|
|
||||||
return AjaxResult.success(page);
|
return AjaxResult.success(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取检查方法列表
|
* 获取检查方法列表
|
||||||
*/
|
*/
|
||||||
@GetMapping({"/method/list", "/check-method/list"})
|
@GetMapping({ "/method/list", "/check-method/list" })
|
||||||
public AjaxResult methodList() {
|
public AjaxResult methodList() {
|
||||||
List<CheckMethod> list = checkMethodService.list();
|
List<CheckMethod> list = checkMethodService.list();
|
||||||
return AjaxResult.success(list);
|
return AjaxResult.success(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取检查部位列表
|
* 获取检查部位列表
|
||||||
*/
|
*/
|
||||||
@GetMapping({"/part/list", "/check-part/list"})
|
@GetMapping({ "/part/list", "/check-part/list" })
|
||||||
public AjaxResult partList() {
|
public AjaxResult partList() {
|
||||||
List<CheckPart> list = checkPartService.list();
|
List<CheckPart> list = checkPartService.list();
|
||||||
return AjaxResult.success(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(
|
public AjaxResult packageList(
|
||||||
@RequestParam(required = false) Integer pageNo,
|
@RequestParam(required = false) Integer pageNo,
|
||||||
@RequestParam(required = false) Integer pageSize,
|
@RequestParam(required = false) Integer pageSize,
|
||||||
@@ -131,9 +236,9 @@ public class CheckTypeController extends BaseController {
|
|||||||
@RequestParam(required = false) String user,
|
@RequestParam(required = false) String user,
|
||||||
@RequestParam(required = false) String startDate,
|
@RequestParam(required = false) String startDate,
|
||||||
@RequestParam(required = false) String endDate) {
|
@RequestParam(required = false) String endDate) {
|
||||||
|
|
||||||
LambdaQueryWrapper<CheckPackage> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<CheckPackage> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
// 添加筛选条件
|
// 添加筛选条件
|
||||||
if (organization != null && !organization.isEmpty()) {
|
if (organization != null && !organization.isEmpty()) {
|
||||||
wrapper.eq(CheckPackage::getOrganization, organization);
|
wrapper.eq(CheckPackage::getOrganization, organization);
|
||||||
@@ -159,20 +264,20 @@ public class CheckTypeController extends BaseController {
|
|||||||
if (endDate != null && !endDate.isEmpty()) {
|
if (endDate != null && !endDate.isEmpty()) {
|
||||||
wrapper.le(CheckPackage::getMaintainDate, LocalDate.parse(endDate));
|
wrapper.le(CheckPackage::getMaintainDate, LocalDate.parse(endDate));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按更新时间倒序排列
|
// 按更新时间倒序排列
|
||||||
wrapper.orderByDesc(CheckPackage::getUpdateTime);
|
wrapper.orderByDesc(CheckPackage::getUpdateTime);
|
||||||
|
|
||||||
// 如果需要分页
|
// 如果需要分页
|
||||||
if (pageNo != null && pageSize != null) {
|
if (pageNo != null && pageSize != null) {
|
||||||
com.baomidou.mybatisplus.extension.plugins.pagination.Page<CheckPackage> page =
|
com.baomidou.mybatisplus.extension.plugins.pagination.Page<CheckPackage> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(
|
||||||
new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNo, pageSize);
|
pageNo, pageSize);
|
||||||
com.baomidou.mybatisplus.extension.plugins.pagination.Page<CheckPackage> result =
|
com.baomidou.mybatisplus.extension.plugins.pagination.Page<CheckPackage> result = checkPackageService
|
||||||
checkPackageService.page(page, wrapper);
|
.page(page, wrapper);
|
||||||
return AjaxResult.success(result);
|
return AjaxResult.success(result);
|
||||||
} else {
|
} else {
|
||||||
List<CheckPackage> list = checkPackageService.list(wrapper);
|
List<CheckPackage> list = checkPackageService.list(wrapper);
|
||||||
return AjaxResult.success(list);
|
return AjaxResult.success(list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +308,7 @@ public class CheckTypeController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 根据ID获取检查套餐详情
|
* 根据ID获取检查套餐详情
|
||||||
*/
|
*/
|
||||||
@GetMapping({"/package/{id}", "/check-package/{id}"})
|
@GetMapping({ "/package/{id}", "/check-package/{id}" })
|
||||||
public R<?> getCheckPackageById(@PathVariable Long id) {
|
public R<?> getCheckPackageById(@PathVariable Long id) {
|
||||||
return checkPackageAppService.getCheckPackageById(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) {
|
public R<?> addCheckPackage(@Valid @RequestBody CheckPackageDto checkPackageDto) {
|
||||||
return checkPackageAppService.addCheckPackage(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) {
|
public R<?> updateCheckPackage(@Valid @RequestBody CheckPackageDto checkPackageDto) {
|
||||||
return checkPackageAppService.updateCheckPackage(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) {
|
public R<?> deleteCheckPackage(@PathVariable Long id) {
|
||||||
return checkPackageAppService.deleteCheckPackage(id);
|
return checkPackageAppService.deleteCheckPackage(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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("删除/作废成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -547,7 +547,7 @@
|
|||||||
null AS skin_test_flag,
|
null AS skin_test_flag,
|
||||||
null AS inject_flag,
|
null AS inject_flag,
|
||||||
null AS group_id,
|
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 volume,
|
||||||
'' AS lot_number,
|
'' AS lot_number,
|
||||||
T1.quantity AS quantity,
|
T1.quantity AS quantity,
|
||||||
|
|||||||
@@ -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 / createTime(INSERT 自动填充)</li>
|
||||||
|
* <li>updateBy / updateTime(UPDATE 自动填充)</li>
|
||||||
|
* <li>tenantId(INSERT 自动填充)</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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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> {
|
||||||
|
}
|
||||||
@@ -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> {
|
||||||
|
}
|
||||||
@@ -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> {
|
||||||
|
}
|
||||||
@@ -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> {
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
}
|
||||||
@@ -0,0 +1,837 @@
|
|||||||
|
<template>
|
||||||
|
<div class="exam-app-container">
|
||||||
|
<!-- ====== 顶部卡片:申请单列表 ====== -->
|
||||||
|
<div class="top-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<span class="section-title">检查项目 ({{ applicationList.length }})</span>
|
||||||
|
<div class="header-actions">
|
||||||
|
<el-button type="primary" @click="handleAdd" icon="Plus">新增</el-button>
|
||||||
|
<el-button type="success" @click="handleSave" icon="Finished">保存</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="applicationList"
|
||||||
|
:max-height="200"
|
||||||
|
highlight-current-row
|
||||||
|
@row-click="handleRowClick"
|
||||||
|
border
|
||||||
|
size="small"
|
||||||
|
:header-cell-style="{ background: '#f5f5f5', color: '#303133', fontWeight: '600' }"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="40" align="center" />
|
||||||
|
<el-table-column label="申请ID" prop="id" width="80" align="center" />
|
||||||
|
<el-table-column label="申请单号" prop="applyNo" min-width="140" align="center" />
|
||||||
|
<el-table-column label="申检部位" prop="inspectionArea" min-width="100" align="center" />
|
||||||
|
<el-table-column label="申请医生" prop="applyDocCode" min-width="90" align="center" />
|
||||||
|
<el-table-column label="急" prop="isUrgent" width="50" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-checkbox v-model="row.isUrgent" :true-label="1" :false-label="0" disabled />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="收费" prop="isCharged" width="50" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-checkbox v-model="row.isCharged" :true-label="1" :false-label="0" disabled />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="退费" prop="isRefunded" width="50" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-checkbox v-model="row.isRefunded" :true-label="1" :false-label="0" disabled />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="执行" prop="isExecuted" width="50" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-checkbox v-model="row.isExecuted" :true-label="1" :false-label="0" disabled />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="金额" prop="totalAmount" width="90" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ (row.totalAmount || 0).toFixed(2) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="80" align="center" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button link @click.stop="handlePrint(row)" title="打印">
|
||||||
|
<el-icon><Printer /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
<el-button link type="danger" @click.stop="handleDelete(row)" title="删除">
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ====== 底部主区:左表单 + 右分类 ====== -->
|
||||||
|
<div class="bottom-section">
|
||||||
|
<!-- 左:表单区 -->
|
||||||
|
<div class="form-panel">
|
||||||
|
<el-tabs v-model="activeDetailTab" class="form-tabs">
|
||||||
|
<!-- TAB1:检查申请单 -->
|
||||||
|
<el-tab-pane label="检查申请单" name="applyForm">
|
||||||
|
<el-form ref="formRef" :model="form" :rules="rules" size="small" class="apply-form">
|
||||||
|
<el-row :gutter="12">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="申请单号" prop="applyNo">
|
||||||
|
<el-input v-model="form.applyNo" readonly />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="姓名" prop="patientName">
|
||||||
|
<el-input v-model="form.patientName" readonly />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="就诊卡号" prop="medicalrecordNumber">
|
||||||
|
<el-input v-model="form.medicalrecordNumber" readonly />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="12">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="费用性质" prop="natureofCost">
|
||||||
|
<el-select v-model="form.natureofCost" style="width:100%">
|
||||||
|
<el-option label="自费医疗" value="自费医疗" />
|
||||||
|
<el-option label="医保报销" value="医保报销" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="申请日期" prop="applyTime">
|
||||||
|
<el-date-picker v-model="form.applyTime" type="date" style="width:100%"
|
||||||
|
format="YYYY-MM-DD" value-format="YYYY-MM-DD HH:mm:ss" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="申请科室" prop="applyDeptCode">
|
||||||
|
<el-input v-model="form.applyDeptCode" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="12">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="申请医生" prop="applyDocCode">
|
||||||
|
<el-input v-model="form.applyDocCode" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="执行科室" prop="performDeptCode">
|
||||||
|
<el-input v-model="form.performDeptCode" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="12">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="诊断描述" prop="clinicDesc">
|
||||||
|
<el-input v-model="form.clinicDesc" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="12">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="禁忌症" prop="contraindication">
|
||||||
|
<el-input v-model="form.contraindication" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="临床诊断" prop="clinicalDiag">
|
||||||
|
<el-input v-model="form.clinicalDiag" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="病史摘要" prop="medicalHistorySummary">
|
||||||
|
<el-input v-model="form.medicalHistorySummary" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="12">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="检查目的" prop="purposeDesc">
|
||||||
|
<el-input v-model="form.purposeDesc" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="12">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="体格检查">
|
||||||
|
<el-input v-model="form.purposeofInspection" placeholder="T(摄氏度) P次/分 R次/分 BF" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="申检部位">
|
||||||
|
<el-input v-model="form.inspectionArea" readonly />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="检查方法">
|
||||||
|
<el-input v-model="form.inspectionMethod" readonly />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="12">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="备注" prop="applyRemark">
|
||||||
|
<el-input v-model="form.applyRemark" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="16">
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-checkbox v-model="form.isUrgent" :true-label="1" :false-label="0">急</el-checkbox>
|
||||||
|
<el-checkbox v-model="form.isCharged" :true-label="1" :false-label="0" disabled>收费</el-checkbox>
|
||||||
|
<el-checkbox v-model="form.isRefunded" :true-label="1" :false-label="0" disabled>退费</el-checkbox>
|
||||||
|
<el-checkbox v-model="form.isExecuted" :true-label="1" :false-label="0" disabled>执行</el-checkbox>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<!-- TAB2:检查明细 -->
|
||||||
|
<el-tab-pane label="检查明细" name="applyDetail">
|
||||||
|
<el-table
|
||||||
|
ref="detailTableRef"
|
||||||
|
:data="selectedItems"
|
||||||
|
border
|
||||||
|
size="small"
|
||||||
|
style="width:100%"
|
||||||
|
:max-height="350"
|
||||||
|
:header-cell-style="{ background: '#f5f5f5', color: '#303133' }"
|
||||||
|
>
|
||||||
|
<el-table-column label="行" type="index" width="45" align="center" />
|
||||||
|
<el-table-column label="检查项目" prop="name" min-width="120" />
|
||||||
|
<el-table-column label="部位" prop="applyPart" min-width="90">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-input v-model="scope.row.applyPart" size="small" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="单位" prop="unit" width="55" align="center" />
|
||||||
|
<el-table-column label="总量" prop="quantity" width="70" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-input-number v-model="scope.row.quantity" :min="1" size="small" :controls="false" style="width:100%" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="单价" prop="price" width="75" align="right" />
|
||||||
|
<el-table-column label="金额" width="80" align="right">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ ((scope.row.price || 0) * (scope.row.quantity || 1)).toFixed(2) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="类型" prop="checkType" width="70" align="center" />
|
||||||
|
<el-table-column label="国码" prop="nationalCode" width="70" align="center" />
|
||||||
|
<el-table-column label="自费" width="50" align="center">
|
||||||
|
<template #default>
|
||||||
|
<el-checkbox disabled />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div class="total-row">
|
||||||
|
合计:<span class="total-amount">{{ totalAmountCalc }}</span>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右:检查项目分类面板 -->
|
||||||
|
<div class="category-panel">
|
||||||
|
<div class="panel-top">
|
||||||
|
<!-- 左侧:分类搜索 + 折叠树 -->
|
||||||
|
<div class="category-left">
|
||||||
|
<div class="panel-label">检查项目分类</div>
|
||||||
|
<el-input
|
||||||
|
v-model="dictSearchKey"
|
||||||
|
placeholder="搜索检查项目(支持拼音首字母)"
|
||||||
|
prefix-icon="Search"
|
||||||
|
clearable
|
||||||
|
size="small"
|
||||||
|
class="search-input"
|
||||||
|
/>
|
||||||
|
<!-- 分类折叠列表 -->
|
||||||
|
<div class="collapse-scroll" v-loading="dictLoading">
|
||||||
|
<div v-if="filteredCategoryList.length === 0" class="empty-hint">
|
||||||
|
{{ dictLoading ? '' : '暂无检查项目,请在"检查项目设置"中配置' }}
|
||||||
|
</div>
|
||||||
|
<el-collapse v-else v-model="activeNames">
|
||||||
|
<el-collapse-item
|
||||||
|
v-for="cat in filteredCategoryList"
|
||||||
|
:key="cat.typeId"
|
||||||
|
:name="cat.typeId"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<span class="cat-title">{{ cat.categoryName }}</span>
|
||||||
|
</template>
|
||||||
|
<div
|
||||||
|
v-for="item in cat.items"
|
||||||
|
:key="item.id"
|
||||||
|
class="item-row"
|
||||||
|
>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="item.checked"
|
||||||
|
@change="(val) => handleItemSelect(val, item, cat)"
|
||||||
|
class="item-checkbox"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
</el-checkbox>
|
||||||
|
<span class="item-price">¥{{ item.price }}</span>
|
||||||
|
</div>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧:已选择 tags -->
|
||||||
|
<div class="selected-panel">
|
||||||
|
<div class="panel-label">已选择:</div>
|
||||||
|
<div class="selected-tags">
|
||||||
|
<div v-if="selectedItems.length === 0" class="empty-selected">–</div>
|
||||||
|
<el-tag
|
||||||
|
v-else
|
||||||
|
v-for="(item, idx) in selectedItems"
|
||||||
|
:key="idx"
|
||||||
|
closable
|
||||||
|
size="small"
|
||||||
|
@close="handleRemoveItem(idx, item)"
|
||||||
|
class="selected-tag"
|
||||||
|
>
|
||||||
|
{{ item.name }} ¥{{ item.price }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, computed, watch, onMounted, nextTick } from 'vue';
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
|
import { Printer, Delete } from '@element-plus/icons-vue';
|
||||||
|
import useUserStore from '@/store/modules/user';
|
||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
patientInfo: { type: Object, default: () => ({}) },
|
||||||
|
activeTab: { type: String, default: '' }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保存成功后通知父组件刷新医嘱列表
|
||||||
|
const emit = defineEmits(['saved']);
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const loading = ref(false);
|
||||||
|
const dictLoading = ref(false);
|
||||||
|
const activeDetailTab = ref('applyForm');
|
||||||
|
const applicationList = ref([]);
|
||||||
|
const selectedItems = ref([]);
|
||||||
|
const detailTableRef = ref(null);
|
||||||
|
const formRef = ref(null);
|
||||||
|
|
||||||
|
// ====== 表单数据 ======
|
||||||
|
const form = reactive({
|
||||||
|
applyNo: '',
|
||||||
|
patientName: '',
|
||||||
|
patientId: '',
|
||||||
|
visitNo: '',
|
||||||
|
applyDeptCode: '',
|
||||||
|
performDeptCode: '',
|
||||||
|
applyDocCode: '',
|
||||||
|
applyTime: '',
|
||||||
|
medicalrecordNumber: '',
|
||||||
|
natureofCost: '自费医疗',
|
||||||
|
clinicDesc: '',
|
||||||
|
contraindication: '',
|
||||||
|
medicalHistorySummary: '',
|
||||||
|
purposeofInspection: '',
|
||||||
|
inspectionArea: '',
|
||||||
|
inspectionMethod: '',
|
||||||
|
applyRemark: '',
|
||||||
|
clinicalDiag: '',
|
||||||
|
purposeDesc: '',
|
||||||
|
isUrgent: 0,
|
||||||
|
pregnancyState: 0,
|
||||||
|
allergyDesc: '',
|
||||||
|
applyStatus: 0,
|
||||||
|
isCharged: 0,
|
||||||
|
isRefunded: 0,
|
||||||
|
isExecuted: 0,
|
||||||
|
examTypeCode: '' // 检查类型编码,必填字段,保存时从已选项目自动推导
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
natureofCost: [{ required: true, message: '请选择费用性质', trigger: 'change' }],
|
||||||
|
applyDeptCode: [{ required: true, message: '请输入申请科室', trigger: 'blur' }],
|
||||||
|
applyDocCode: [{ required: true, message: '请输入申请医生', trigger: 'blur' }],
|
||||||
|
performDeptCode: [{ required: true, message: '请输入执行科室', trigger: 'blur' }],
|
||||||
|
clinicDesc: [{ required: true, message: '请输入诊断描述', trigger: 'blur' }],
|
||||||
|
clinicalDiag: [{ required: true, message: '请输入临床诊断', trigger: 'blur' }],
|
||||||
|
medicalHistorySummary: [{ required: true, message: '请输入病史摘要', trigger: 'blur' }],
|
||||||
|
purposeDesc: [{ required: true, message: '请输入检查目的', trigger: 'blur' }]
|
||||||
|
};
|
||||||
|
|
||||||
|
// ====== 检查项目分类 ======
|
||||||
|
const categoryList = ref([]); // 原始分类+项目数据
|
||||||
|
const dictSearchKey = ref('');
|
||||||
|
const activeNames = ref([]); // 当前展开的折叠项
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadCategoryList();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载检查类型(分类)和检查项目(部位/项目),按类型分组展示
|
||||||
|
*/
|
||||||
|
async function loadCategoryList() {
|
||||||
|
dictLoading.value = true;
|
||||||
|
try {
|
||||||
|
// 1. 加载检查类型(分类名称),只取父级
|
||||||
|
const typeRes = await request({
|
||||||
|
url: '/system/check-type/list',
|
||||||
|
method: 'get',
|
||||||
|
params: { pageNo: 1, pageSize: 500 } // 取全量分类数据
|
||||||
|
});
|
||||||
|
let types = [];
|
||||||
|
if (typeRes.data?.records) types = typeRes.data.records;
|
||||||
|
else if (Array.isArray(typeRes.data)) types = typeRes.data;
|
||||||
|
else if (Array.isArray(typeRes.rows)) types = typeRes.rows;
|
||||||
|
|
||||||
|
// 2. 加载检查项目(检查部位项目)
|
||||||
|
const partRes = await request({ url: '/check/part/list', method: 'get' });
|
||||||
|
let parts = [];
|
||||||
|
if (Array.isArray(partRes)) parts = partRes;
|
||||||
|
else if (Array.isArray(partRes.data?.data)) parts = partRes.data.data; // 双层嵌套:{ data: { data: [...] } }
|
||||||
|
else if (Array.isArray(partRes.data)) parts = partRes.data;
|
||||||
|
else if (Array.isArray(partRes.rows)) parts = partRes.rows;
|
||||||
|
else if (partRes.data?.records) parts = partRes.data.records;
|
||||||
|
|
||||||
|
// 3. 按 checkType 归类
|
||||||
|
const dict = [];
|
||||||
|
for (const t of types) {
|
||||||
|
dict.push({
|
||||||
|
typeId: t.id,
|
||||||
|
typeCode: t.type,
|
||||||
|
categoryName: t.name,
|
||||||
|
items: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const unclassified = [];
|
||||||
|
for (const p of parts) {
|
||||||
|
const mapped = {
|
||||||
|
id: p.id,
|
||||||
|
name: p.name,
|
||||||
|
price: p.price || 0,
|
||||||
|
serviceFee: p.serviceFee || 0,
|
||||||
|
unit: '次',
|
||||||
|
checkType: p.checkType || '',
|
||||||
|
nationalCode: p.nationalCode || '',
|
||||||
|
checked: false
|
||||||
|
};
|
||||||
|
const target = dict.find(d => d.typeCode === p.checkType);
|
||||||
|
if (target) target.items.push(mapped);
|
||||||
|
else unclassified.push(mapped);
|
||||||
|
}
|
||||||
|
if (unclassified.length > 0) {
|
||||||
|
dict.push({ typeId: 'uncls', typeCode: '', categoryName: '其他', items: unclassified });
|
||||||
|
}
|
||||||
|
categoryList.value = dict.filter(d => d.items.length > 0);
|
||||||
|
|
||||||
|
// 默认展开第一个
|
||||||
|
if (categoryList.value.length > 0) {
|
||||||
|
activeNames.value = [categoryList.value[0].typeId];
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('加载检查项目分类失败', err);
|
||||||
|
} finally {
|
||||||
|
dictLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 关键词过滤后的分类列表 */
|
||||||
|
const filteredCategoryList = computed(() => {
|
||||||
|
if (!dictSearchKey.value) return categoryList.value;
|
||||||
|
const key = dictSearchKey.value.toLowerCase();
|
||||||
|
return categoryList.value.map(cat => ({
|
||||||
|
...cat,
|
||||||
|
items: cat.items.filter(item => (item.name || '').toLowerCase().includes(key))
|
||||||
|
})).filter(cat => cat.items.length > 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ====== 合计 ======
|
||||||
|
const totalAmountCalc = computed(() => {
|
||||||
|
const total = selectedItems.value.reduce((sum, item) => {
|
||||||
|
return sum + (item.price * (item.quantity || 1));
|
||||||
|
}, 0);
|
||||||
|
return total.toFixed(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听已选项:自动更新申检部位
|
||||||
|
watch(selectedItems, () => {
|
||||||
|
form.inspectionArea = selectedItems.value.map(i => i.name).join('+');
|
||||||
|
form.isCharged = selectedItems.value.length > 0 ? 1 : 0;
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
// 监听患者变化
|
||||||
|
watch(() => props.patientInfo, (newVal) => {
|
||||||
|
if (newVal?.encounterId) {
|
||||||
|
initPatientForm(newVal);
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
}, { immediate: true, deep: true });
|
||||||
|
|
||||||
|
watch(() => props.activeTab, (val) => {
|
||||||
|
if (val === 'examination') getList();
|
||||||
|
});
|
||||||
|
|
||||||
|
function initPatientForm(patient) {
|
||||||
|
form.patientName = patient.patientName || '';
|
||||||
|
form.medicalrecordNumber = patient.busNo || patient.visitNo || '';
|
||||||
|
form.patientId = patient.patientId || '';
|
||||||
|
form.visitNo = patient.visitNo || '';
|
||||||
|
form.applyDeptCode = userStore.orgName || patient.organizationName || '';
|
||||||
|
form.applyDocCode = userStore.nickName || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 申请单 CRUD ======
|
||||||
|
function getList() {
|
||||||
|
loading.value = true;
|
||||||
|
request({
|
||||||
|
url: '/exam/apply/list',
|
||||||
|
method: 'get',
|
||||||
|
params: { visitNo: props.patientInfo?.visitNo || '' }
|
||||||
|
}).then(res => {
|
||||||
|
applicationList.value = res.rows || res.data || [];
|
||||||
|
}).catch(err => console.error('获取申请单列表失败', err))
|
||||||
|
.finally(() => { loading.value = false; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAdd() {
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
Object.assign(form, {
|
||||||
|
applyNo: '', patientId: props.patientInfo?.patientId || '',
|
||||||
|
visitNo: props.patientInfo?.visitNo || '',
|
||||||
|
applyDeptCode: userStore.orgName || '',
|
||||||
|
performDeptCode: '',
|
||||||
|
applyDocCode: userStore.nickName || '',
|
||||||
|
applyTime: new Date().toISOString().split('T')[0] + ' 12:00:00',
|
||||||
|
medicalrecordNumber: props.patientInfo?.busNo || '',
|
||||||
|
natureofCost: '自费医疗',
|
||||||
|
clinicDesc: '', contraindication: '', medicalHistorySummary: '',
|
||||||
|
purposeofInspection: '', inspectionArea: '', inspectionMethod: '',
|
||||||
|
applyRemark: '', clinicalDiag: '', purposeDesc: '',
|
||||||
|
isUrgent: 0, pregnancyState: 0, allergyDesc: '',
|
||||||
|
applyStatus: 0, isCharged: 0, isRefunded: 0, isExecuted: 0
|
||||||
|
});
|
||||||
|
selectedItems.value = [];
|
||||||
|
resetCategoryChecked();
|
||||||
|
activeDetailTab.value = 'applyForm';
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSave() {
|
||||||
|
formRef.value.validate(valid => {
|
||||||
|
if (!valid) return;
|
||||||
|
if (selectedItems.value.length === 0) {
|
||||||
|
ElMessage.warning('请至少选择一个检查明细项目');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 从已选项目推导检查类型编码(取第一个项目的 checkType,如 CT / ECG / GI)
|
||||||
|
const firstCheckType = selectedItems.value[0]?.checkType || 'unknown';
|
||||||
|
if (!form.examTypeCode) form.examTypeCode = firstCheckType;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
...form,
|
||||||
|
encounterId: props.patientInfo?.encounterId || null,
|
||||||
|
patientIdNum: props.patientInfo?.patientId || null,
|
||||||
|
items: selectedItems.value.map((item, index) => ({
|
||||||
|
itemCode: String(item.id),
|
||||||
|
itemName: item.name,
|
||||||
|
bodyPartCode: item.checkType || 'unknown',
|
||||||
|
itemFee: item.price,
|
||||||
|
performDeptCode: form.performDeptCode || '',
|
||||||
|
itemStatus: 0,
|
||||||
|
itemSeq: index + 1
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
request({
|
||||||
|
url: '/exam/apply',
|
||||||
|
method: payload.applyNo ? 'put' : 'post',
|
||||||
|
data: payload
|
||||||
|
}).then(res => {
|
||||||
|
ElMessage.success('保存成功');
|
||||||
|
getList();
|
||||||
|
if (res.data) form.applyNo = res.data;
|
||||||
|
// 通知父组件刷新医嘱列表
|
||||||
|
emit('saved');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRowClick(row) {
|
||||||
|
Object.assign(form, row);
|
||||||
|
selectedItems.value = [];
|
||||||
|
activeDetailTab.value = 'applyForm';
|
||||||
|
request({ url: `/exam/apply/${row.applyNo}`, method: 'get' }).then(res => {
|
||||||
|
const d = res.data || res;
|
||||||
|
if (d.data) Object.assign(form, d.data);
|
||||||
|
if (d.items && Array.isArray(d.items)) {
|
||||||
|
selectedItems.value = d.items.map(m => ({
|
||||||
|
id: m.itemCode, name: m.itemName,
|
||||||
|
price: m.itemFee || 0, quantity: 1,
|
||||||
|
serviceFee: 0, unit: '次',
|
||||||
|
applyPart: m.itemName,
|
||||||
|
checkType: m.bodyPartCode || '',
|
||||||
|
nationalCode: '', checked: true
|
||||||
|
}));
|
||||||
|
syncCategoryChecked();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePrint(row) { ElMessage.info('打印申请单:' + row.applyNo); }
|
||||||
|
|
||||||
|
function handleDelete(row) {
|
||||||
|
ElMessageBox.confirm('确认删除该检查申请单吗?', '警告', { type: 'warning' }).then(() => {
|
||||||
|
request({ url: `/exam/apply/${row.applyNo}`, method: 'delete' }).then(() => {
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
getList();
|
||||||
|
if (form.applyNo === row.applyNo) handleAdd();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 勾选逻辑 ======
|
||||||
|
function handleItemSelect(checked, item, cat) {
|
||||||
|
if (checked) {
|
||||||
|
selectedItems.value.push({
|
||||||
|
id: item.id, name: item.name,
|
||||||
|
price: item.price, quantity: 1,
|
||||||
|
serviceFee: item.serviceFee || 0,
|
||||||
|
unit: item.unit || '次',
|
||||||
|
applyPart: item.name,
|
||||||
|
checkType: cat.typeCode || '',
|
||||||
|
nationalCode: item.nationalCode || '',
|
||||||
|
checked: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const idx = selectedItems.value.findIndex(s => s.id === item.id);
|
||||||
|
if (idx > -1) selectedItems.value.splice(idx, 1);
|
||||||
|
}
|
||||||
|
// 有选项时切换到明细tab
|
||||||
|
if (selectedItems.value.length > 0) {
|
||||||
|
activeDetailTab.value = 'applyDetail';
|
||||||
|
nextTick(() => detailTableRef.value?.doLayout());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRemoveItem(idx, item) {
|
||||||
|
selectedItems.value.splice(idx, 1);
|
||||||
|
// 取消对应 category 中的 checkbox
|
||||||
|
for (const cat of categoryList.value) {
|
||||||
|
const found = cat.items.find(x => x.id === item.id);
|
||||||
|
if (found) { found.checked = false; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetCategoryChecked() {
|
||||||
|
for (const cat of categoryList.value)
|
||||||
|
for (const item of cat.items) item.checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncCategoryChecked() {
|
||||||
|
resetCategoryChecked();
|
||||||
|
const ids = new Set(selectedItems.value.map(s => s.id));
|
||||||
|
for (const cat of categoryList.value)
|
||||||
|
for (const item of cat.items)
|
||||||
|
if (ids.has(item.id)) item.checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ getList });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.exam-app-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px;
|
||||||
|
height: 100%;
|
||||||
|
background: #f0f2f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶部申请单列表 */
|
||||||
|
.top-section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||||
|
}
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部区域:左表单 + 右分类 */
|
||||||
|
.bottom-section {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左:表单面板 */
|
||||||
|
.form-panel {
|
||||||
|
flex: 1;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||||
|
overflow-y: auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.form-tabs :deep(.el-tabs__header) {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.apply-form :deep(.el-form-item) {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.apply-form :deep(.el-form-item__label) {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.total-row {
|
||||||
|
margin-top: 8px;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
.total-amount {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #f56c6c;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右:分类面板 */
|
||||||
|
.category-panel {
|
||||||
|
width: 380px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||||
|
padding: 10px 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.panel-top {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.category-left {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.panel-label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.search-input {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.collapse-scroll {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.empty-hint {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 检查项目分类折叠 */
|
||||||
|
.cat-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
.item-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 3px 4px;
|
||||||
|
}
|
||||||
|
.item-row:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
.item-checkbox {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.item-checkbox :deep(.el-checkbox__label) {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
.item-price {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #1890FF;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 已选择 tags */
|
||||||
|
.selected-panel {
|
||||||
|
width: 120px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.selected-tags {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.selected-tag {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.empty-selected {
|
||||||
|
color: #c0c4cc;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 折叠组件细节 */
|
||||||
|
:deep(.el-collapse) {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
:deep(.el-collapse-item__header) {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 6px 0;
|
||||||
|
height: auto;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
:deep(.el-collapse-item__content) {
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
:deep(.el-collapse-item__wrap) {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -161,6 +161,10 @@
|
|||||||
<el-tab-pane label="检验" name="inspection">
|
<el-tab-pane label="检验" name="inspection">
|
||||||
<inspectionApplication :patientInfo="patientInfo" :activeTab="activeTab" ref="inspectionRef" />
|
<inspectionApplication :patientInfo="patientInfo" :activeTab="activeTab" ref="inspectionRef" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="检查" name="examination">
|
||||||
|
<examinationApplication :patientInfo="patientInfo" :activeTab="activeTab" ref="examinationRef"
|
||||||
|
@saved="() => prescriptionRef?.getListInfo()" />
|
||||||
|
</el-tab-pane>
|
||||||
<el-tab-pane label="手术申请" name="surgery">
|
<el-tab-pane label="手术申请" name="surgery">
|
||||||
<surgeryApplication :patientInfo="patientInfo" :activeTab="activeTab" ref="surgeryRef" />
|
<surgeryApplication :patientInfo="patientInfo" :activeTab="activeTab" ref="surgeryRef" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
@@ -215,6 +219,7 @@ import eprescriptionlist from './components/eprescriptionlist.vue';
|
|||||||
import HospitalizationDialog from './components/hospitalizationDialog.vue';
|
import HospitalizationDialog from './components/hospitalizationDialog.vue';
|
||||||
import tcmAdvice from './components/tcm/tcmAdvice.vue';
|
import tcmAdvice from './components/tcm/tcmAdvice.vue';
|
||||||
import inspectionApplication from './components/inspection/inspectionApplication.vue';
|
import inspectionApplication from './components/inspection/inspectionApplication.vue';
|
||||||
|
import examinationApplication from './components/examination/examinationApplication.vue';
|
||||||
import surgeryApplication from './components/surgery/surgeryApplication.vue';
|
import surgeryApplication from './components/surgery/surgeryApplication.vue';
|
||||||
import DoctorCallDialog from './components/callQueue/DoctorCallDialog.vue';
|
import DoctorCallDialog from './components/callQueue/DoctorCallDialog.vue';
|
||||||
import { formatDate, formatDateStr } from '@/utils/index';
|
import { formatDate, formatDateStr } from '@/utils/index';
|
||||||
@@ -306,6 +311,7 @@ const patientDrawerRef = ref();
|
|||||||
const prescriptionRef = ref();
|
const prescriptionRef = ref();
|
||||||
const tcmRef = ref();
|
const tcmRef = ref();
|
||||||
const inspectionRef = ref();
|
const inspectionRef = ref();
|
||||||
|
const examinationRef = ref();
|
||||||
const surgeryRef = ref();
|
const surgeryRef = ref();
|
||||||
const emrRef = ref();
|
const emrRef = ref();
|
||||||
const diagnosisRef = ref();
|
const diagnosisRef = ref();
|
||||||
@@ -496,6 +502,11 @@ function handleClick(tab) {
|
|||||||
inspectionRef.value.getList();
|
inspectionRef.value.getList();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'examination':
|
||||||
|
if (patientInfo.value && patientInfo.value.encounterId) {
|
||||||
|
examinationRef.value.getList();
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'surgery':
|
case 'surgery':
|
||||||
surgeryRef.value.getList();
|
surgeryRef.value.getList();
|
||||||
break;
|
break;
|
||||||
@@ -607,6 +618,7 @@ function handleCardClick(item, index) {
|
|||||||
prescriptionRef.value.getListInfo();
|
prescriptionRef.value.getListInfo();
|
||||||
tcmRef.value.getListInfo();
|
tcmRef.value.getListInfo();
|
||||||
inspectionRef.value.getList();
|
inspectionRef.value.getList();
|
||||||
|
if(examinationRef.value) examinationRef.value.getList();
|
||||||
surgeryRef.value.getList();
|
surgeryRef.value.getList();
|
||||||
diagnosisRef.value.getList();
|
diagnosisRef.value.getList();
|
||||||
eprescriptionRef.value.getList();
|
eprescriptionRef.value.getList();
|
||||||
|
|||||||
Reference in New Issue
Block a user