feat(card): 实现医生个人报卡管理系统

- 添加医生个人报卡统计、分页查询、提交、撤回、删除功能
- 实现批量提交和删除报卡操作
- 添加报卡导出为Word文档功能
- 新增DoctorCardStatisticsDto、DoctorCardListDto等数据传输对象
- 在InfectiousCardDto中添加状态文本字段
- 优化报卡状态显示,将"待审核"改为"已提交"并新增"作废"状态
- 添加多个DTO类的getter/setter方法以确保序列化正常工作
- 实现医生权限验证确保只能操作自己的报卡
- 完善报卡状态流转控制和业务逻辑验证
This commit is contained in:
2026-03-09 14:57:45 +08:00
parent c3776c642b
commit d99188bfb9
24 changed files with 1080 additions and 8 deletions

View File

@@ -93,4 +93,75 @@ public interface ICardManageAppService {
* @return 科室树数据
*/
R<?> getDeptTree();
/**
* 获取医生个人报卡统计数据
*
* @return 统计数据
*/
DoctorCardStatisticsDto getDoctorCardStatistics();
/**
* 分页查询医生个人报卡列表
*
* @param queryParams 查询参数
* @return 分页数据
*/
R<?> getDoctorCardPage(DoctorCardQueryDto queryParams);
/**
* 提交报卡
*
* @param cardNo 卡片编号
* @return 结果
*/
R<?> submitCard(String cardNo);
/**
* 撤回报卡
*
* @param cardNo 卡片编号
* @return 结果
*/
R<?> withdrawCard(String cardNo);
/**
* 删除报卡(状态变为作废)
*
* @param cardNo 卡片编号
* @return 结果
*/
R<?> deleteCard(String cardNo);
/**
* 批量提交报卡
*
* @param cardNos 卡片编号列表
* @return 结果
*/
R<?> batchSubmitCards(List<String> cardNos);
/**
* 批量删除报卡
*
* @param cardNos 卡片编号列表
* @return 结果
*/
R<?> batchDeleteCards(List<String> cardNos);
/**
* 导出报卡为Word文档
*
* @param cardNo 卡片编号
* @param response 响应
*/
void exportCardToWord(String cardNo, HttpServletResponse response);
/**
* 更新医生报卡
*
* @param updateDto 更新参数
* @return 结果
*/
R<?> updateDoctorCard(DoctorCardUpdateDto updateDto);
}

View File

@@ -308,15 +308,340 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
return R.ok(new ArrayList<>());
}
/**
* 转换为DTO
*/
private InfectiousCardDto convertToDto(InfectiousCard card) {
InfectiousCardDto dto = new InfectiousCardDto();
BeanUtils.copyProperties(card, dto);
@Override
public DoctorCardStatisticsDto getDoctorCardStatistics() {
Long doctorId = SecurityUtils.getUserId();
DoctorCardStatisticsDto dto = new DoctorCardStatisticsDto();
dto.setTotalCount(infectiousCardMapper.countByDoctorId(doctorId));
dto.setPendingFailedCount(infectiousCardMapper.countPendingFailedByDoctorId(doctorId));
dto.setReportedCount(infectiousCardMapper.countReportedByDoctorId(doctorId));
return dto;
}
@Override
public R<?> getDoctorCardPage(DoctorCardQueryDto queryParams) {
Long doctorId = SecurityUtils.getUserId();
Page<InfectiousCard> page = new Page<>(queryParams.getPageNo(), queryParams.getPageSize());
LambdaQueryWrapper<InfectiousCard> wrapper = new LambdaQueryWrapper<>();
// 只查询当前医生的报卡
wrapper.eq(InfectiousCard::getDoctorId, doctorId);
// 状态筛选
if (StringUtils.hasText(queryParams.getStatus())) {
wrapper.eq(InfectiousCard::getStatus, queryParams.getStatus());
}
// 时间范围筛选
if (StringUtils.hasText(queryParams.getStartDate())) {
LocalDateTime startDateTime = LocalDateTime.parse(queryParams.getStartDate() + "T00:00:00");
wrapper.ge(InfectiousCard::getCreateTime, startDateTime);
}
if (StringUtils.hasText(queryParams.getEndDate())) {
LocalDateTime endDateTime = LocalDateTime.parse(queryParams.getEndDate() + "T23:59:59");
wrapper.le(InfectiousCard::getCreateTime, endDateTime);
}
// 关键词搜索(患者姓名或报卡名称)
if (StringUtils.hasText(queryParams.getKeyword())) {
wrapper.and(w -> w
.like(InfectiousCard::getPatName, queryParams.getKeyword())
.or()
.like(InfectiousCard::getDiseaseName, queryParams.getKeyword())
);
}
// 按创建时间倒序
wrapper.orderByDesc(InfectiousCard::getCreateTime);
IPage<InfectiousCard> result = infectiousCardMapper.selectPage(page, wrapper);
// 转换为DTO
List<DoctorCardListDto> list = result.getRecords().stream()
.map(this::convertToDoctorCardListDto)
.collect(Collectors.toList());
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("list", list);
resultMap.put("total", result.getTotal());
return R.ok(resultMap);
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> submitCard(String cardNo) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) {
return R.fail("报卡不存在");
}
// 验证权限:只能提交自己的报卡
if (!card.getDoctorId().equals(SecurityUtils.getUserId())) {
return R.fail("无权操作此报卡");
}
// 验证状态:只有暂存状态可以提交
if (!"0".equals(card.getStatus())) {
return R.fail("只能提交暂存状态的报卡");
}
// 更新状态为已提交
card.setStatus("1");
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
return R.ok("提交成功");
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> withdrawCard(String cardNo) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) {
return R.fail("报卡不存在");
}
// 验证权限:只能撤回自己的报卡
if (!card.getDoctorId().equals(SecurityUtils.getUserId())) {
return R.fail("无权操作此报卡");
}
// 验证状态:只有已提交状态可以撤回
if (!"1".equals(card.getStatus())) {
return R.fail("只能撤回已提交状态的报卡");
}
// 更新状态为暂存
card.setStatus("0");
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
return R.ok("撤回成功");
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> deleteCard(String cardNo) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) {
return R.fail("报卡不存在");
}
// 验证权限:只能删除自己的报卡
if (!card.getDoctorId().equals(SecurityUtils.getUserId())) {
return R.fail("无权操作此报卡");
}
// 验证状态:只有暂存状态可以删除
if (!"0".equals(card.getStatus())) {
return R.fail("只能删除暂存状态的报卡");
}
// 更新状态为作废
card.setStatus("6");
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
return R.ok("删除成功");
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> batchSubmitCards(List<String> cardNos) {
if (cardNos == null || cardNos.isEmpty()) {
return R.fail("请选择要提交的报卡");
}
Long doctorId = SecurityUtils.getUserId();
int successCount = 0;
for (String cardNo : cardNos) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) continue;
// 验证权限:只能提交自己的报卡
if (!card.getDoctorId().equals(doctorId)) continue;
// 验证状态:只有暂存状态可以提交
if (!"0".equals(card.getStatus())) continue;
// 更新状态为已提交
card.setStatus("1");
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
successCount++;
}
if (successCount == 0) {
return R.fail("没有可提交的报卡,只能提交暂存状态的报卡");
}
return R.ok("批量提交成功,共提交" + successCount + "");
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> batchDeleteCards(List<String> cardNos) {
if (cardNos == null || cardNos.isEmpty()) {
return R.fail("请选择要删除的报卡");
}
Long doctorId = SecurityUtils.getUserId();
int successCount = 0;
for (String cardNo : cardNos) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) continue;
// 验证权限:只能删除自己的报卡
if (!card.getDoctorId().equals(doctorId)) continue;
// 验证状态:只有暂存状态可以删除
if (!"0".equals(card.getStatus())) continue;
// 更新状态为作废
card.setStatus("6");
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
successCount++;
}
if (successCount == 0) {
return R.fail("没有可删除的报卡,只能删除暂存状态的报卡");
}
return R.ok("批量删除成功,共删除" + successCount + "");
}
@Override
public R<?> updateDoctorCard(DoctorCardUpdateDto updateDto) {
// 获取当前登录用户信息
LoginUser loginUser = SecurityUtils.getLoginUser();
Long currentUserId = loginUser.getUserId();
// 查询报卡
InfectiousCard card = infectiousCardMapper.selectByCardNo(updateDto.getCardNo());
if (card == null) {
return R.fail("报卡不存在");
}
// 验证是否当前医生的报卡 - 根据doctorId字段验证
if (!currentUserId.equals(card.getDoctorId())) {
return R.fail("只能修改自己的报卡");
}
// 验证状态是否允许修改(只能修改暂存状态的报卡)
if (!"0".equals(card.getStatus())) {
return R.fail("只能修改暂存状态的报卡");
}
// 更新字段
card.setPhone(updateDto.getPhone());
card.setOnsetDate(updateDto.getOnsetDate());
card.setDiagDate(updateDto.getDiagDate());
card.setDiseaseType(updateDto.getDiseaseType()); // 使用diseaseType字段
card.setAddressProv(updateDto.getAddressProv());
card.setAddressCity(updateDto.getAddressCity());
card.setAddressCounty(updateDto.getAddressCounty());
card.setAddressHouse(updateDto.getAddressHouse());
card.setUpdateTime(LocalDateTime.now());
card.setUpdateBy(loginUser.getUsername()); // 使用username作为更新者
int rows = infectiousCardMapper.updateById(card);
if (rows > 0) {
return R.ok("更新成功");
}
return R.fail("更新失败");
}
// 验证是否当前医生的报卡
// 使用createBy字段验证(通常是创建人),如果使用了其他字段则需调整
if (!currentUserId.equals(card.getCreateBy())) {
return R.fail("只能修改自己的报卡");
}
// 验证状态是否允许修改(只能修改暂存状态的报卡)
if (!"0".equals(card.getStatus())) {
return R.fail("只能修改暂存状态的报卡");
}
// 更新字段
card.setPhone(updateDto.getPhone());
card.setOnsetDate(updateDto.getOnsetDate());
card.setDiagDate(updateDto.getDiagDate());
card.setAddressProv(updateDto.getAddressProv());
card.setAddressCity(updateDto.getAddressCity());
card.setAddressCounty(updateDto.getAddressCounty());
card.setAddressHouse(updateDto.getAddressHouse());
card.setUpdateTime(LocalDateTime.now());
card.setUpdateBy(currentUserId);
int rows = infectiousCardMapper.updateById(card);
if (rows > 0) {
return R.ok("更新成功");
}
return R.fail("更新失败");
}
@Override
public void exportCardToWord(String cardNo, HttpServletResponse response) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) {
return;
}
// 验证权限:只能导出自己的报卡
if (!card.getDoctorId().equals(SecurityUtils.getUserId())) {
return;
}
// 验证状态:只有已上报状态可以导出
if (!"3".equals(card.getStatus())) {
return;
}
try {
// TODO: 实现Word导出逻辑使用Apache POI或其他库
// 这里简化为返回文本内容
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.setHeader("Content-Disposition", "attachment;filename=" +
URLEncoder.encode("传染病报告卡-" + cardNo + ".docx", StandardCharsets.UTF_8));
// 实际应生成Word文档内容
response.getWriter().write("报卡编号:" + cardNo);
} catch (IOException e) {
log.error("导出报卡失败", e);
}
}
/**
* 转换为医生报卡列表DTO
*/
private DoctorCardListDto convertToDoctorCardListDto(InfectiousCard card) {
DoctorCardListDto dto = new DoctorCardListDto();
BeanUtils.copyProperties(card, dto);
dto.setCardName(getCardName(card.getCardNameCode()));
dto.setSubmitTime(card.getCreateTime() != null ?
new SimpleDateFormat("yyyy-MM-dd HH:mm").format(card.getCreateTime()) : null);
return dto;
}
/**
* 获取报卡名称
*/
private String getCardName(Integer cardNameCode) {
if (cardNameCode == null) return "中华人民共和国传染病报告卡";
switch (cardNameCode) {
case 1: return "中华人民共和国传染病报告卡";
case 2: return "甲类传染病报告卡";
case 3: return "乙类传染病报告卡";
case 4: return "丙类传染病报告卡";
default: return "中华人民共和国传染病报告卡";
}
}
/**
* 转换审核记录为DTO
*/
@@ -327,6 +652,16 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
return dto;
}
/**
* 转换为报卡DTO
*/
private InfectiousCardDto convertToDto(InfectiousCard card) {
InfectiousCardDto dto = new InfectiousCardDto();
BeanUtils.copyProperties(card, dto);
dto.setStatusText(getStatusText(card.getStatus()));
return dto;
}
/**
* 创建审核记录
*/
@@ -355,11 +690,12 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
private String getStatusText(String status) {
switch (status) {
case "0": return "暂存";
case "1": return "待审核";
case "1": return "已提交";
case "2": return "审核通过";
case "3": return "已上报";
case "4": return "失败";
case "5": return "审核失败";
case "6": return "作废";
default: return "未知";
}
}

View File

@@ -134,4 +134,104 @@ public class CardManageController {
public R<?> getDeptTree() {
return cardManageAppService.getDeptTree();
}
// ==================== 医生个人报卡管理 ====================
/**
* 获取医生个人报卡统计数据
*
* @return 统计数据
*/
@GetMapping("/doctor/statistics")
public R<DoctorCardStatisticsDto> getDoctorCardStatistics() {
return R.ok(cardManageAppService.getDoctorCardStatistics());
}
/**
* 分页查询医生个人报卡列表
*
* @param queryParams 查询参数
* @return 分页数据
*/
@GetMapping("/doctor/page")
public R<?> getDoctorCardPage(DoctorCardQueryDto queryParams) {
return cardManageAppService.getDoctorCardPage(queryParams);
}
/**
* 提交报卡
*
* @param cardNo 卡片编号
* @return 结果
*/
@PostMapping("/doctor/submit/{cardNo}")
public R<?> submitCard(@PathVariable String cardNo) {
return cardManageAppService.submitCard(cardNo);
}
/**
* 撤回报卡
*
* @param cardNo 卡片编号
* @return 结果
*/
@PostMapping("/doctor/withdraw/{cardNo}")
public R<?> withdrawCard(@PathVariable String cardNo) {
return cardManageAppService.withdrawCard(cardNo);
}
/**
* 删除报卡(状态变为作废)
*
* @param cardNo 卡片编号
* @return 结果
*/
@DeleteMapping("/doctor/{cardNo}")
public R<?> deleteCard(@PathVariable String cardNo) {
return cardManageAppService.deleteCard(cardNo);
}
/**
* 批量提交报卡
*
* @param cardNos 卡片编号列表
* @return 结果
*/
@PostMapping("/doctor/batch-submit")
public R<?> batchSubmitCards(@RequestBody List<String> cardNos) {
return cardManageAppService.batchSubmitCards(cardNos);
}
/**
* 批量删除报卡
*
* @param cardNos 卡片编号列表
* @return 结果
*/
@PostMapping("/doctor/batch-delete")
public R<?> batchDeleteCards(@RequestBody List<String> cardNos) {
return cardManageAppService.batchDeleteCards(cardNos);
}
/**
* 更新医生报卡
*
* @param updateDto 更新参数
* @return 结果
*/
@PostMapping("/doctor/update")
public R<?> updateDoctorCard(@RequestBody DoctorCardUpdateDto updateDto) {
return cardManageAppService.updateDoctorCard(updateDto);
}
/**
* 导出报卡为Word文档
*
* @param cardNo 卡片编号
* @param response 响应
*/
@GetMapping("/doctor/export-word/{cardNo}")
public void exportCardToWord(@PathVariable String cardNo, HttpServletResponse response) {
cardManageAppService.exportCardToWord(cardNo, response);
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright ©2026 CJB-CNIT Team. All rights reserved
*/
package com.openhis.web.cardmanagement.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
/**
* 医生个人报卡列表DTO
*
* @author system
* @date 2026-03-08
*/
@Data
public class DoctorCardListDto {
/** 卡片ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/** 卡片编号 */
private String cardNo;
/** 患者姓名 */
private String patName;
/** 身份证号 */
private String idNo;
/** 联系电话 */
private String phone;
/** 就诊卡号(暂时不展示,字段保留) */
private String visitCardNo;
/** 报卡名称 */
private String cardName;
/** 提交时间 */
private String submitTime;
/** 状态 */
private String status;
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright ©2026 CJB-CNIT Team. All rights reserved
*/
package com.openhis.web.cardmanagement.dto;
import lombok.Data;
/**
* 医生个人报卡查询参数
*
* @author system
* @date 2026-03-08
*/
@Data
public class DoctorCardQueryDto {
/** 页码 */
private Integer pageNo = 1;
/** 每页数量 */
private Integer pageSize = 10;
/** 开始日期 */
private String startDate;
/** 结束日期 */
private String endDate;
/** 状态(0暂存/1已提交/2已审核/3已上报/4失败/5退回) */
private String status;
/** 患者姓名或报卡名称 */
private String keyword;
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright ©2026 CJB-CNIT Team. All rights reserved
*/
package com.openhis.web.cardmanagement.dto;
import lombok.Data;
/**
* 医生个人报卡统计数据
*
* @author system
* @date 2026-03-08
*/
@Data
public class DoctorCardStatisticsDto {
/** 总报卡数 */
private Integer totalCount;
/** 待处理失败数 */
private Integer pendingFailedCount;
/** 已成功上报数 */
private Integer reportedCount;
}

View File

@@ -113,6 +113,9 @@ public class InfectiousCardDto {
/** 状态 */
private String status;
/** 状态文本 */
private String statusText;
/** 报卡名称代码 */
private Integer cardNameCode;

View File

@@ -47,4 +47,22 @@ public interface InfectiousCardMapper extends BaseMapper<InfectiousCard> {
*/
@Select("SELECT * FROM infectious_card WHERE card_no = #{cardNo}")
InfectiousCard selectByCardNo(@Param("cardNo") String cardNo);
/**
* 统计医生个人总报卡数
*/
@Select("SELECT COUNT(*) FROM infectious_card WHERE doctor_id = #{doctorId}")
Integer countByDoctorId(@Param("doctorId") Long doctorId);
/**
* 统计医生待处理失败数状态为0暂存或4失败
*/
@Select("SELECT COUNT(*) FROM infectious_card WHERE doctor_id = #{doctorId} AND status IN ('0', '4')")
Integer countPendingFailedByDoctorId(@Param("doctorId") Long doctorId);
/**
* 统计医生已成功上报数状态为3已上报
*/
@Select("SELECT COUNT(*) FROM infectious_card WHERE doctor_id = #{doctorId} AND status = '3'")
Integer countReportedByDoctorId(@Param("doctorId") Long doctorId);
}