接口路径:
POST /api/report/savePOST /api/report/remote-save功能说明: 正式保存报告(书写、审核、确认),更新报告内容、状态和操作痕迹。
public function updateReport($examId, $data, $type, $reportType, $token, $ra_id = null)
{
// 1. 权限验证
$user = $this->reportDao->getUser($token);
$result = $this->reportAuthentication($user_id, $type, $exam['institution_id'], $reportType);
if (!$result) {
throw new Exception('权限未分配');
}
// 2. 获取或创建报告记录
$report = $this->reportDao->getReport($examId, $rt, $ra_id);
if (empty($reportId)) {
if ($type == 'save') {
// 自动创建 report 记录
$insert_data = [
'id' => UUIDUtils::uuid(),
'exam_id' => $examId,
'createdAt' => date('Y-m-d H:i:s'),
'report_result' => 1,
'type' => $rt, // 1=本地, 2=远程
'remote_application_id' => $ra_id
];
$this->reportDao->insertReport($insert_data);
$reportId = $insert_data['id'];
}
} else {
// 3. 防止并发冲突
switch ($type) {
case 'save':
if ($user_id !== $report['report_doctor_id'] && !empty($report['report_doctor_id'])) {
throw new Exception('该报告已被其他医师书写');
}
break;
case 'audit':
if ($user_id !== $report['review_doctor_id'] && !empty($report['review_doctor_id'])) {
throw new Exception('该报告已被其他医师审核');
}
break;
}
}
// 4. 更新患者基本信息(save和audit时)
if ($type != 'confirm') {
$update = [
'name' => $data['name'],
'sex' => $data['sex'],
'age' => $data['age'],
'phone' => $data['phone'],
'exam_class' => $data['exam_class'],
'exam_project' => $data['exam_project']
];
if ($rt == 1) {
// 本地报告:更新 exams 表
$this->reportDao->updateExamMsg($examId, $exam['patient_id'], $update);
} else {
// 远程报告:更新 remote_application 表
$this->reportDao->updateRemote($ra_id, $update);
}
}
// 5. 更新报告数据
$report_up = [
'impression' => $data['impression'],
'description' => $data['description'],
'report_result' => $data['report_result'],
'hr_status' => $data['hr_status']
];
$this->updateReportData($examId, $report_up, $type, $exam['exam_status'], $user, $reportType, $ra_id);
// 6. 确认报告时的特殊处理
if ($type == 'confirm') {
// 删除Redis缓存
$this->reportDao->delCache($reportId . '_local');
// 发送短信通知
if ($sms == '1' && !empty($phone)) {
$this->sendSms($phone, $reportId, $examId, $studyId, $insName, $user['institution_id']);
}
// 生成二维码
$url = $wechat_url . '/wx_patient/api/unifyGetWxQrcode?reportId=' . $reportId . '&version=3';
$this->curl_get($url);
// 远程报告:处理订单和费用
if ($reportType == 1 && !empty($ra_id)) {
$money = $this->reportDao->getOrderMoney($ra_id);
// 增加机构余额
$this->reportDao->updateInstititonMoney($user['institution_id'], $current_money + $money);
// 更新订单状态
$this->reportDao->updateOrder($ra_id, ORDER_STATUS_CONFIRM);
// 微信推送
$wechat->pushWechatComplete($order_id);
}
}
// 7. 删除草稿
if (isset($reportId) && !empty($reportId)) {
$key = $this->getStageKey($reportId, $user_id);
$stage = $this->reportDao->getStage($key);
if ($stage) {
$this->reportDao->delStage($key);
}
}
return true;
}
public function updateReportData($exam_id, $data, $type, $exam_status, $user, $report_type, $id = null)
{
// 根据操作类型设置不同的更新字段
switch ($type) {
case 'save': // 书写报告
// 状态验证
if ($report_type == 2) { // 本地
if ($exam_status != 3 && $exam_status != 7 && $exam_status != 12) {
throw new Exception('流程顺序错误,无法书写报告');
}
} else { // 远程
if ($status != 6 && $status != 7) {
throw new Exception('流程顺序错误,无法书写报告');
}
}
$update = [
'impression' => $data['impression'],
'description' => $data['description'],
'hr_status' => $data['hr_status'],
'report_result' => $data['report_result'],
'report_datetime' => date('Y-m-d H:i:s'),
'report_doctor_id' => $user['id'],
'report_doctor_name' => $user['realname']
];
$exam_up_status = 7; // 已写报告
$trace_type = 1; // 痕迹类型:书写
break;
case 'audit': // 审核报告
if ($report_type == 2) {
if ($exam_status != 8 && $exam_status != 7) {
throw new Exception('流程顺序错误,无法审核报告');
}
}
$update = [
'impression' => $data['impression'],
'description' => $data['description'],
'hr_status' => $data['hr_status'],
'report_result' => $data['report_result'],
'review_datetime' => date('Y-m-d H:i:s'),
'review_doctor_id' => $user['id'],
'review_doctor_name' => $user['realname']
];
$exam_up_status = 8; // 已审报告
$trace_type = 2; // 痕迹类型:审核
break;
case 'confirm': // 确认报告
if ($report_type == 2) {
if ($exam_status != 8) {
throw new Exception('流程顺序错误,无法确认报告');
}
}
$update = [
'confirm_datetime' => date('Y-m-d H:i:s'),
'confirm_doctor_id' => $user['id'],
'confirm_doctor_name' => $user['realname']
];
$exam_up_status = 9; // 已确认报告
$trace_type = 3; // 痕迹类型:确认
break;
}
// 更新 report 表
if ($report_type == '2') { // 本地
$this->reportDao->updateReport($exam_id, 1, $update);
// 更新 exams 表的 exam_status
$this->reportDao->updateExamStatus($exam_id, $exam_up_status);
} else { // 远程
$this->reportDao->updateRemoteReport($id, $update);
// 更新 remote_application 表的 report_status
$this->reportDao->updateRemoteStatus($id, $exam_up_status);
}
// 添加报告操作痕迹
$trace = [
'id' => UUIDUtils::uuid(),
'impression' => $update['impression'] ?? '',
'description' => $update['description'] ?? '',
'report_result' => $update['report_result'] ?? '',
'report_id' => $rid,
'createdAt' => date('Y-m-d H:i:s'),
'doctor_id' => $user['id'],
'type' => $trace_type // 1=书写,2=审核,3=确认
];
$this->reportDao->insertReportRecord($trace);
return true;
}
@Data
public class ReportSaveVO {
@NotBlank(message = "检查ID不能为空")
private String examId;
@NotNull(message = "报告类型不能为空")
private Integer isRemote; // 1=远程,2=本地
private String raId; // 远程申请ID(远程报告时必填)
// 患者基本信息
@NotBlank(message = "患者姓名不能为空")
private String name;
@NotBlank(message = "性别不能为空")
private String sex;
@NotBlank(message = "年龄不能为空")
private String age;
private String phone;
@NotBlank(message = "检查类型不能为空")
private String examClass;
private String examProject;
// 报告内容
@NotBlank(message = "诊断印象不能为空")
private String impression;
@NotBlank(message = "影像所见不能为空")
private String description;
@NotNull(message = "报告结果不能为空")
private Integer reportResult;
private Integer hrStatus; // HR状态,默认1
}
@Data
public class ReportConfirmVO {
@NotBlank(message = "检查ID不能为空")
private String examId;
@NotNull(message = "报告类型不能为空")
private Integer isRemote;
private String raId;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveReport(ReportSaveVO saveVO, String type) {
// type: "save"=书写, "audit"=审核, "confirm"=确认
String examId = saveVO.getExamId();
Integer isRemote = saveVO.getIsRemote();
String raId = saveVO.getRaId();
// 1. 获取当前登录用户
String currentUserId = getCurrentUserId();
SysUser currentUser = sysUserService.getById(currentUserId);
Doctors doctor = doctorsMapper.selectOne(
new LambdaQueryWrapper<Doctors>()
.eq(Doctors::getUid, currentUserId)
.last("LIMIT 1")
);
// 2. 权限验证
boolean hasPermission = checkReportPermission(currentUserId, type, isRemote);
if (!hasPermission) {
throw new RuntimeException("权限未分配,请联系管理员");
}
// 3. 获取或创建报告记录
Report report;
if (isRemote == 2) {
// 本地报告
report = reportMapper.selectOne(
new LambdaQueryWrapper<Report>()
.eq(Report::getExamId, examId)
.eq(Report::getType, "1")
.last("LIMIT 1")
);
} else {
// 远程报告
report = reportMapper.selectOne(
new LambdaQueryWrapper<Report>()
.eq(Report::getRemoteApplicationId, raId)
.last("LIMIT 1")
);
}
String reportId;
if (report == null) {
// 自动创建报告记录(仅在书写时)
if ("save".equals(type)) {
report = new Report();
report.setExamId(examId);
report.setCreateTime(new Date());
report.setReportResult(1);
report.setType(isRemote == 2 ? "1" : "2");
if (isRemote == 1) {
report.setRemoteApplicationId(raId);
}
reportMapper.insert(report);
reportId = report.getId();
} else {
throw new RuntimeException("报告不存在,无法进行" + getTypeDesc(type));
}
} else {
reportId = report.getId();
// 4. 防止并发冲突
checkConcurrentConflict(report, doctor.getId(), type);
}
// 5. 更新患者基本信息(书写和审核时)
if (!"confirm".equals(type)) {
updatePatientInfo(examId, isRemote, raId, saveVO);
}
// 6. 更新报告数据
updateReportData(examId, reportId, isRemote, raId, saveVO, type, doctor);
// 7. 确认报告时的特殊处理
if ("confirm".equals(type)) {
handleConfirm(reportId, examId, isRemote, raId, doctor);
}
// 8. 删除草稿
deleteDraft(reportId, doctor.getId());
return true;
}
/**
* 检查并发冲突
*/
private void checkConcurrentConflict(Report report, String doctorId, String type) {
switch (type) {
case "save":
if (StringUtils.isNotBlank(report.getReportDoctorId())
&& !doctorId.equals(report.getReportDoctorId())) {
throw new RuntimeException("该报告已被其他医师书写,请刷新页面进行查看");
}
break;
case "audit":
if (StringUtils.isNotBlank(report.getReviewDoctorId())
&& !doctorId.equals(report.getReviewDoctorId())) {
throw new RuntimeException("该报告已被其他医师审核,请刷新页面进行查看");
}
break;
}
}
/**
* 更新患者基本信息
*/
private void updatePatientInfo(String examId, Integer isRemote, String raId, ReportSaveVO saveVO) {
// 性别转换
String sex = saveVO.getSex();
if ("男".equals(sex)) {
sex = "M";
} else if ("女".equals(sex)) {
sex = "F";
}
if (isRemote == 2) {
// 本地报告:更新 exams 表
Exam exam = new Exam();
exam.setId(examId);
exam.setName(saveVO.getName());
exam.setSex(sex);
exam.setAge(saveVO.getAge());
exam.setPhone(saveVO.getPhone());
exam.setExamClass(saveVO.getExamClass());
exam.setExamProject(saveVO.getExamProject());
examMapper.updateById(exam);
} else {
// 远程报告:更新 remote_application 表
RemoteApplication ra = new RemoteApplication();
ra.setId(raId);
ra.setName(saveVO.getName());
ra.setSex(sex);
ra.setAge(saveVO.getAge());
ra.setPhone(saveVO.getPhone());
ra.setExamClass(saveVO.getExamClass());
ra.setExamProject(saveVO.getExamProject());
remoteApplicationMapper.updateById(ra);
}
}
/**
* 更新报告数据
*/
private void updateReportData(String examId, String reportId, Integer isRemote,
String raId, ReportSaveVO saveVO, String type, Doctors doctor) {
Report updateReport = new Report();
updateReport.setId(reportId);
Integer examStatus;
Integer traceType;
switch (type) {
case "save": // 书写
// 验证流程状态
validateStatus(examId, isRemote, raId, type);
updateReport.setImpression(saveVO.getImpression());
updateReport.setDescription(saveVO.getDescription());
updateReport.setReportResult(saveVO.getReportResult());
updateReport.setHrStatus(saveVO.getHrStatus() != null ? saveVO.getHrStatus() : 1);
updateReport.setReportDatetime(new Date());
updateReport.setReportDoctorId(doctor.getId());
updateReport.setReportDoctorName(doctor.getRealname());
examStatus = 7; // 已写报告
traceType = 1; // 书写痕迹
break;
case "audit": // 审核
validateStatus(examId, isRemote, raId, type);
updateReport.setImpression(saveVO.getImpression());
updateReport.setDescription(saveVO.getDescription());
updateReport.setReportResult(saveVO.getReportResult());
updateReport.setHrStatus(saveVO.getHrStatus() != null ? saveVO.getHrStatus() : 1);
updateReport.setReviewDatetime(new Date());
updateReport.setReviewDoctorId(doctor.getId());
updateReport.setReviewDoctorName(doctor.getRealname());
examStatus = 8; // 已审报告
traceType = 2; // 审核痕迹
break;
case "confirm": // 确认
validateStatus(examId, isRemote, raId, type);
updateReport.setConfirmDatetime(new Date());
updateReport.setConfirmDoctorId(doctor.getId());
updateReport.setConfirmDoctorName(doctor.getRealname());
examStatus = 9; // 已确认报告
traceType = 3; // 确认痕迹
break;
default:
throw new RuntimeException("未知的操作类型: " + type);
}
// 更新 report 表
reportMapper.updateById(updateReport);
// 更新状态
updateExamStatus(examId, isRemote, raId, examStatus);
// 添加操作痕迹
addReportTrace(reportId, saveVO, doctor.getId(), traceType);
}
/**
* 添加报告操作痕迹
*/
private void addReportTrace(String reportId, ReportSaveVO saveVO, String doctorId, Integer type) {
ReportRecord trace = new ReportRecord();
trace.setReportId(reportId);
trace.setImpression(saveVO.getImpression());
trace.setDescription(saveVO.getDescription());
trace.setReportResult(saveVO.getReportResult());
trace.setCreateTime(new Date());
trace.setDoctorId(doctorId);
trace.setType(type); // 1=书写,2=审核,3=确认
reportRecordMapper.insert(trace);
}
/**
* 删除草稿
*/
private void deleteDraft(String reportId, String doctorId) {
LambdaQueryWrapper<ReportTemp> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ReportTemp::getReportId, reportId)
.eq(ReportTemp::getDoctorId, doctorId);
reportTempMapper.delete(wrapper);
}
/**
* 确认报告的特殊处理
*/
private void handleConfirm(String reportId, String examId, Integer isRemote,
String raId, Doctors doctor) {
// TODO: 发送短信通知
// TODO: 生成二维码
// TODO: 远程报告处理订单和费用
// TODO: 微信推送通知
}
/**
* 书写报告
*/
@PostMapping("/save")
@Operation(summary = "书写报告", description = "保存报告内容,更新为已写状态")
@SystemLogHandler("书写报告|保存")
public RestResult<?> saveReport(@RequestBody @Valid ReportSaveVO saveVO) {
try {
boolean result = reportService.saveReport(saveVO, "save");
return RestResult.ok("保存成功");
} catch (Exception e) {
return RestResult.error("保存失败:" + e.getMessage());
}
}
/**
* 审核报告
*/
@PostMapping("/audit")
@Operation(summary = "审核报告", description = "审核报告内容,更新为已审状态")
@SystemLogHandler("审核报告|保存")
public RestResult<?> auditReport(@RequestBody @Valid ReportSaveVO saveVO) {
try {
boolean result = reportService.saveReport(saveVO, "audit");
return RestResult.ok("审核成功");
} catch (Exception e) {
return RestResult.error("审核失败:" + e.getMessage());
}
}
/**
* 确认报告
*/
@PostMapping("/confirm")
@Operation(summary = "确认报告", description = "确认报告,更新为已确认状态")
@SystemLogHandler("确认报告|保存")
public RestResult<?> confirmReport(@RequestBody @Valid ReportConfirmVO confirmVO) {
try {
boolean result = reportService.confirmReport(confirmVO);
return RestResult.ok("确认成功");
} catch (Exception e) {
return RestResult.error("确认失败:" + e.getMessage());
}
}
3(已登记) → 7(已写报告) → 8(已审报告) → 9(已确认报告)
↑______________|
(可重新书写/审核)
6(待书写) → 7(已写报告) → 8(已审报告) → 10(待确认) → 9(已确认报告)
| type | 操作类型 | 说明 |
|---|---|---|
| 1 | 书写 | 报告医生书写报告 |
| 2 | 审核 | 审核医生审核报告 |
| 3 | 确认 | 确认医生确认报告 |