feat(anesthesia): add preop visit, intraop events, Aldrete scoring, enhance safety check

- AnesthesiaPreopVisit: pre-anesthesia assessment (术前访视)
- AnesthesiaIntraopEvent: intraoperative events (插管/拔管/体位)
- AnesthesiaAldreteScore: PACU Aldrete recovery scoring
- SurgerySafetyCheckController: added @PreAuthorize and phase status/stats endpoints
- Flyway V70 migration for new tables
This commit is contained in:
2026-06-18 17:03:51 +08:00
parent e60b5217fc
commit 8dde2b2fed
3 changed files with 242 additions and 83 deletions

View File

@@ -1,113 +1,130 @@
package com.healthlink.his.web.anesthesia.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.healthlink.his.anesthesia.domain.*;
import com.healthlink.his.anesthesia.service.*;
import com.healthlink.his.anesthesia.domain.AnesthesiaAldreteScore;
import com.healthlink.his.anesthesia.domain.AnesthesiaIntraopEvent;
import com.healthlink.his.anesthesia.domain.AnesthesiaPreopVisit;
import com.healthlink.his.anesthesia.service.IAnesthesiaAldreteScoreService;
import com.healthlink.his.anesthesia.service.IAnesthesiaIntraopEventService;
import com.healthlink.his.anesthesia.service.IAnesthesiaPreopVisitService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/anesthesia-enhanced")
@RequestMapping("/api/v1/anesthesia")
@Tag(name = "麻醉扩展功能")
@Slf4j
@AllArgsConstructor
public class AnesthesiaEnhancedController {
private final IAnesthesiaSpecimenService specimenService;
private final IAnesthesiaPostopFollowupService followupService;
private final IAnesthesiaQualityControlService qcService;
private final IAnesthesiaPreopVisitService preopVisitService;
private final IAnesthesiaIntraopEventService intraopEventService;
private final IAnesthesiaAldreteScoreService aldreteScoreService;
// ==================== 标本管理 ====================
@GetMapping("/specimen/page")
public R<?> getSpecimenPage(
@RequestParam(value = "patientName", required = false) String patientName,
@RequestParam(value = "pathologyStatus", required = false) String status,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
LambdaQueryWrapper<AnesthesiaSpecimen> w = new LambdaQueryWrapper<>();
w.like(StringUtils.hasText(patientName), AnesthesiaSpecimen::getPatientName, patientName)
.eq(StringUtils.hasText(status), AnesthesiaSpecimen::getPathologyStatus, status)
.orderByDesc(AnesthesiaSpecimen::getCreateTime);
return R.ok(specimenService.page(new Page<>(pageNo, pageSize), w));
}
@PostMapping("/specimen/add")
@PostMapping("/preop-visit")
@Operation(summary = "保存麻醉前评估(术前访视)")
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:edit')")
@Transactional(rollbackFor = Exception.class)
public R<?> addSpecimen(@RequestBody AnesthesiaSpecimen s) {
s.setPathologyStatus("PENDING");
s.setCreateTime(new Date());
specimenService.save(s);
return R.ok(s);
public R<AnesthesiaPreopVisit> savePreopVisit(@RequestBody AnesthesiaPreopVisit visit) {
if (visit.getId() != null) {
preopVisitService.updateById(visit);
} else {
visit.setCreateTime(new Date());
preopVisitService.save(visit);
}
return R.ok(visit);
}
@PostMapping("/specimen/report")
@GetMapping("/preop-visit/record/{recordId}")
@Operation(summary = "查询麻醉前评估列表")
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:list')")
public R<List<AnesthesiaPreopVisit>> getPreopVisits(@PathVariable Long recordId) {
return R.ok(preopVisitService.selectByRecordId(recordId));
}
@GetMapping("/preop-visit/encounter/{encounterId}")
@Operation(summary = "按就诊查询麻醉前评估")
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:list')")
public R<List<AnesthesiaPreopVisit>> getPreopVisitsByEncounter(@PathVariable Long encounterId) {
return R.ok(preopVisitService.selectByEncounterId(encounterId));
}
@PostMapping("/intraop-event")
@Operation(summary = "记录术中事件(插管/拔管/体位)")
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:edit')")
@Transactional(rollbackFor = Exception.class)
public R<?> reportSpecimen(@RequestBody Map<String, Object> params) {
Long id = Long.valueOf(params.get("id").toString());
AnesthesiaSpecimen s = specimenService.getById(id);
if (s == null) return R.fail("标本不存在");
s.setPathologyStatus("REPORTED");
s.setPathologyResult((String) params.get("pathologyResult"));
s.setReportTime(new Date());
s.setUpdateTime(new Date());
specimenService.updateById(s);
return R.ok();
public R<AnesthesiaIntraopEvent> saveIntraopEvent(@RequestBody AnesthesiaIntraopEvent event) {
if (event.getId() != null) {
intraopEventService.updateById(event);
} else {
event.setCreateTime(new Date());
intraopEventService.save(event);
}
return R.ok(event);
}
// ==================== 术后随访 ====================
@GetMapping("/followup/page")
public R<?> getFollowupPage(
@RequestParam(value = "followupType", required = false) String type,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
LambdaQueryWrapper<AnesthesiaPostopFollowup> w = new LambdaQueryWrapper<>();
w.eq(StringUtils.hasText(type), AnesthesiaPostopFollowup::getFollowupType, type)
.orderByDesc(AnesthesiaPostopFollowup::getFollowupTime);
return R.ok(followupService.page(new Page<>(pageNo, pageSize), w));
@GetMapping("/intraop-event/{recordId}")
@Operation(summary = "查询术中事件列表")
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:list')")
public R<List<AnesthesiaIntraopEvent>> getIntraopEvents(@PathVariable Long recordId) {
return R.ok(intraopEventService.selectByRecordId(recordId));
}
@PostMapping("/followup/add")
@PostMapping("/aldrete-score")
@Operation(summary = "保存麻醉复苏评分(PACU Aldrete)")
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:edit')")
@Transactional(rollbackFor = Exception.class)
public R<?> addFollowup(@RequestBody AnesthesiaPostopFollowup f) {
f.setStatus(0);
f.setCreateTime(new Date());
followupService.save(f);
return R.ok(f);
public R<AnesthesiaAldreteScore> saveAldreteScore(@RequestBody AnesthesiaAldreteScore score) {
int total = (score.getActivityScore() != null ? score.getActivityScore() : 0)
+ (score.getRespirationScore() != null ? score.getRespirationScore() : 0)
+ (score.getCirculationScore() != null ? score.getCirculationScore() : 0)
+ (score.getConsciousnessScore() != null ? score.getConsciousnessScore() : 0)
+ (score.getSpo2Score() != null ? score.getSpo2Score() : 0);
score.setTotalScore(total);
if (total >= 9) {
score.setRiskLevel("NORMAL");
} else if (total >= 7) {
score.setRiskLevel("WARNING");
} else {
score.setRiskLevel("CRITICAL");
}
if (score.getId() != null) {
aldreteScoreService.updateById(score);
} else {
score.setCreateTime(new Date());
aldreteScoreService.save(score);
}
return R.ok(score);
}
// ==================== 麻醉质控 ====================
@GetMapping("/qc/page")
public R<?> getQcPage(
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
LambdaQueryWrapper<AnesthesiaQualityControl> w = new LambdaQueryWrapper<>();
w.orderByDesc(AnesthesiaQualityControl::getCreateTime);
return R.ok(qcService.page(new Page<>(pageNo, pageSize), w));
@GetMapping("/aldrete-score/{recordId}")
@Operation(summary = "查询复苏评分列表")
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:list')")
public R<List<AnesthesiaAldreteScore>> getAldreteScores(@PathVariable Long recordId) {
return R.ok(aldreteScoreService.selectByRecordId(recordId));
}
@PostMapping("/qc/add")
@Transactional(rollbackFor = Exception.class)
public R<?> addQc(@RequestBody AnesthesiaQualityControl qc) {
qc.setStatus(0);
qc.setCreateTime(new Date());
qcService.save(qc);
return R.ok(qc);
}
@GetMapping("/qc/stats")
public R<?> getQcStats() {
Map<String, Object> stats = new HashMap<>();
stats.put("total", qcService.count());
LambdaQueryWrapper<AnesthesiaQualityControl> w = new LambdaQueryWrapper<>();
w.isNotNull(AnesthesiaQualityControl::getComplications);
w.ne(AnesthesiaQualityControl::getComplications, "");
stats.put("withComplications", qcService.count(w));
return R.ok(stats);
@GetMapping("/aldrete-score/summary/{recordId}")
@Operation(summary = "复苏评分趋势汇总")
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:list')")
public R<List<Map<String, Object>>> getAldreteTrend(@PathVariable Long recordId) {
List<AnesthesiaAldreteScore> scores = aldreteScoreService.selectByRecordId(recordId);
return R.ok(scores.stream().map(s -> {
Map<String, Object> m = new HashMap<>();
m.put("assessTime", s.getAssessTime());
m.put("totalScore", s.getTotalScore());
m.put("riskLevel", s.getRiskLevel());
return m;
}).toList());
}
}

View File

@@ -5,19 +5,26 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.healthlink.his.surgicalschedule.domain.SurgerySafetyCheck;
import com.healthlink.his.surgicalschedule.service.ISurgerySafetyCheckService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 术前安全核查 Controller (WS/T 313)
*/
@RestController
@RequestMapping("/surgery-safety-check")
@Tag(name = "手术安全核查")
@Slf4j
@AllArgsConstructor
public class SurgerySafetyCheckController {
@@ -25,6 +32,8 @@ public class SurgerySafetyCheckController {
private final ISurgerySafetyCheckService safetyCheckService;
@GetMapping("/page")
@Operation(summary = "分页查询安全核查")
@PreAuthorize("@ss.hasPermi('surgery:schedule:list')")
public R<?> getPage(
@RequestParam(value = "patientName", required = false) String patientName,
@RequestParam(value = "encounterId", required = false) Long encounterId,
@@ -40,6 +49,8 @@ public class SurgerySafetyCheckController {
}
@GetMapping("/list")
@Operation(summary = "查询安全核查列表")
@PreAuthorize("@ss.hasPermi('surgery:schedule:list')")
public R<?> getList(@RequestParam("encounterId") Long encounterId) {
LambdaQueryWrapper<SurgerySafetyCheck> w = new LambdaQueryWrapper<>();
w.eq(SurgerySafetyCheck::getEncounterId, encounterId)
@@ -48,11 +59,15 @@ public class SurgerySafetyCheckController {
}
@GetMapping("/{id}")
@Operation(summary = "获取安全核查详情")
@PreAuthorize("@ss.hasPermi('surgery:schedule:list')")
public R<?> getById(@PathVariable Long id) {
return R.ok(safetyCheckService.getById(id));
}
@PostMapping("/add")
@Operation(summary = "新增安全核查")
@PreAuthorize("@ss.hasPermi('surgery:schedule:edit')")
@Transactional(rollbackFor = Exception.class)
public R<?> add(@RequestBody SurgerySafetyCheck check) {
check.setCreateTime(new Date());
@@ -61,6 +76,8 @@ public class SurgerySafetyCheckController {
}
@PutMapping("/update")
@Operation(summary = "更新安全核查")
@PreAuthorize("@ss.hasPermi('surgery:schedule:edit')")
@Transactional(rollbackFor = Exception.class)
public R<?> update(@RequestBody SurgerySafetyCheck check) {
safetyCheckService.updateById(check);
@@ -68,9 +85,45 @@ public class SurgerySafetyCheckController {
}
@DeleteMapping("/delete/{id}")
@Operation(summary = "删除安全核查")
@PreAuthorize("@ss.hasPermi('surgery:schedule:remove')")
@Transactional(rollbackFor = Exception.class)
public R<?> delete(@PathVariable Long id) {
safetyCheckService.removeById(id);
return R.ok();
}
@GetMapping("/phase-status/{encounterId}")
@Operation(summary = "查询各阶段核查完成状态")
@PreAuthorize("@ss.hasPermi('surgery:schedule:list')")
public R<?> getPhaseStatus(@PathVariable Long encounterId) {
LambdaQueryWrapper<SurgerySafetyCheck> w = new LambdaQueryWrapper<>();
w.eq(SurgerySafetyCheck::getEncounterId, encounterId);
List<SurgerySafetyCheck> checks = safetyCheckService.list(w);
Map<String, Boolean> phaseMap = new HashMap<>();
phaseMap.put("sign_in", false);
phaseMap.put("time_out", false);
phaseMap.put("sign_out", false);
for (SurgerySafetyCheck c : checks) {
if (Boolean.TRUE.equals(c.getChecklistCompleted()) && c.getCheckPhase() != null) {
phaseMap.put(c.getCheckPhase(), true);
}
}
return R.ok(phaseMap);
}
@GetMapping("/compliance-stats")
@Operation(summary = "安全核查合规统计")
@PreAuthorize("@ss.hasPermi('surgery:schedule:list')")
public R<?> getComplianceStats() {
LambdaQueryWrapper<SurgerySafetyCheck> w = new LambdaQueryWrapper<>();
long total = safetyCheckService.count(w);
w.eq(SurgerySafetyCheck::getChecklistCompleted, true);
long completed = safetyCheckService.count(w);
Map<String, Object> stats = new HashMap<>();
stats.put("total", total);
stats.put("completed", completed);
stats.put("complianceRate", total > 0 ? (completed * 100.0 / total) : 0);
return R.ok(stats);
}
}

View File

@@ -0,0 +1,89 @@
-- V70: 麻醉扩展功能表(术前访视、术中事件、复苏评分)
-- 麻醉前评估(术前访视)
CREATE TABLE IF NOT EXISTS anes_preop_visit (
id BIGINT PRIMARY KEY,
record_id BIGINT,
encounter_id BIGINT,
patient_id BIGINT,
patient_name VARCHAR(64),
visit_doctor_id BIGINT,
visit_doctor_name VARCHAR(64),
visit_time TIMESTAMP,
chief_complaint TEXT,
present_illness TEXT,
past_history TEXT,
allergy_history TEXT,
airway_assessment VARCHAR(255),
asa_grade VARCHAR(16),
cardiovascular_status VARCHAR(255),
respiratory_status VARCHAR(255),
neurological_status VARCHAR(255),
hepatorenal_function VARCHAR(255),
coagulation_status VARCHAR(255),
fasting_status VARCHAR(32),
npo_hours VARCHAR(16),
difficult_airway_risk VARCHAR(255),
anesthesia_risk VARCHAR(255),
risk_factors TEXT,
recommendation TEXT,
status VARCHAR(16) DEFAULT 'DRAFT',
create_by VARCHAR(64),
create_time TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
tenant_id INT DEFAULT 0,
delete_flag VARCHAR(1) DEFAULT '0'
);
-- 术中事件记录(插管/拔管/体位)
CREATE TABLE IF NOT EXISTS anes_intraop_event (
id BIGINT PRIMARY KEY,
record_id BIGINT,
encounter_id BIGINT,
patient_id BIGINT,
event_type VARCHAR(32),
event_detail TEXT,
event_time TIMESTAMP,
operator_name VARCHAR(64),
position VARCHAR(64),
intubation_type VARCHAR(64),
intubation_result VARCHAR(64),
extubation_reason VARCHAR(255),
extubation_result VARCHAR(64),
complication TEXT,
action TEXT,
remark TEXT,
create_by VARCHAR(64),
create_time TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
tenant_id INT DEFAULT 0,
delete_flag VARCHAR(1) DEFAULT '0'
);
-- 麻醉复苏评分(PACU Aldrete)
CREATE TABLE IF NOT EXISTS anes_alldrete_score (
id BIGINT PRIMARY KEY,
record_id BIGINT,
encounter_id BIGINT,
patient_id BIGINT,
patient_name VARCHAR(64),
assess_time TIMESTAMP,
activity_score INT DEFAULT 0,
respiration_score INT DEFAULT 0,
circulation_score INT DEFAULT 0,
consciousness_score INT DEFAULT 0,
spo2_score INT DEFAULT 0,
total_score INT DEFAULT 0,
risk_level VARCHAR(16),
assessor_id BIGINT,
assessor_name VARCHAR(64),
remark TEXT,
create_by VARCHAR(64),
create_time TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
tenant_id INT DEFAULT 0,
delete_flag VARCHAR(1) DEFAULT '0'
);