From 8dde2b2fed5a6e2a319d9d57e5e92c62a6b1ad7e Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 17:03:51 +0800 Subject: [PATCH] feat(anesthesia): add preop visit, intraop events, Aldrete scoring, enhance safety check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../AnesthesiaEnhancedController.java | 183 ++++++++++-------- .../SurgerySafetyCheckController.java | 53 +++++ .../db/migration/V70__anesthesia_enhanced.sql | 89 +++++++++ 3 files changed, 242 insertions(+), 83 deletions(-) create mode 100644 healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V70__anesthesia_enhanced.sql diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/anesthesia/controller/AnesthesiaEnhancedController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/anesthesia/controller/AnesthesiaEnhancedController.java index 3fd452830..a8fdaa1e4 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/anesthesia/controller/AnesthesiaEnhancedController.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/anesthesia/controller/AnesthesiaEnhancedController.java @@ -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 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 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> 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> 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 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 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 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> 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 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 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> 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 stats = new HashMap<>(); - stats.put("total", qcService.count()); - LambdaQueryWrapper 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>> getAldreteTrend(@PathVariable Long recordId) { + List scores = aldreteScoreService.selectByRecordId(recordId); + return R.ok(scores.stream().map(s -> { + Map m = new HashMap<>(); + m.put("assessTime", s.getAssessTime()); + m.put("totalScore", s.getTotalScore()); + m.put("riskLevel", s.getRiskLevel()); + return m; + }).toList()); } } diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/surgicalschedule/controller/SurgerySafetyCheckController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/surgicalschedule/controller/SurgerySafetyCheckController.java index 42986ab44..e703e26c9 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/surgicalschedule/controller/SurgerySafetyCheckController.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/surgicalschedule/controller/SurgerySafetyCheckController.java @@ -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 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 w = new LambdaQueryWrapper<>(); + w.eq(SurgerySafetyCheck::getEncounterId, encounterId); + List checks = safetyCheckService.list(w); + Map 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 w = new LambdaQueryWrapper<>(); + long total = safetyCheckService.count(w); + w.eq(SurgerySafetyCheck::getChecklistCompleted, true); + long completed = safetyCheckService.count(w); + Map 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); + } } diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V70__anesthesia_enhanced.sql b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V70__anesthesia_enhanced.sql new file mode 100644 index 000000000..882c39f7e --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V70__anesthesia_enhanced.sql @@ -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' +);