From 8dde2b2fed5a6e2a319d9d57e5e92c62a6b1ad7e Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 17:03:51 +0800 Subject: [PATCH 01/10] 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' +); From 1e4838076e9db17ab72b966f628bd32129793577 Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 17:05:37 +0800 Subject: [PATCH 02/10] =?UTF-8?q?feat(lis-pacs):=20=E7=A1=AE=E8=AE=A4LIS+P?= =?UTF-8?q?ACS=E8=83=BD=E5=8A=9B=E5=AE=8C=E6=95=B4=EF=BC=8C=E8=A1=A5?= =?UTF-8?q?=E9=BD=90@PreAuthorize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ExamAppointmentController.java | 23 +++++++++++--- .../RadiologyEnhancedController.java | 2 ++ .../Reconstruction3DController.java | 31 +++++++++++++++---- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/check/controller/ExamAppointmentController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/check/controller/ExamAppointmentController.java index 990606c81..25e680de0 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/check/controller/ExamAppointmentController.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/check/controller/ExamAppointmentController.java @@ -5,6 +5,7 @@ import com.core.common.core.domain.R; import com.healthlink.his.check.domain.ExamAppointment; import com.healthlink.his.check.service.IExamAppointmentService; 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.*; @@ -12,6 +13,7 @@ import java.util.*; public class ExamAppointmentController { private final IExamAppointmentService appointmentService; @GetMapping("/page") + @PreAuthorize("@ss.hasPermi('infection:check:list')") public R getPage(@RequestParam(value="status",required=false) String status, @RequestParam(value="patientName",required=false) String patientName, @RequestParam(value="appointDate",required=false) String appointDate, @@ -23,7 +25,9 @@ public class ExamAppointmentController { .orderByAsc(ExamAppointment::getQueueNumber); return R.ok(appointmentService.page(new Page<>(pageNo, pageSize), w)); } - @PostMapping("/appoint") @Transactional(rollbackFor=Exception.class) + @PostMapping("/appoint") + @PreAuthorize("@ss.hasPermi('infection:check:edit')") + @Transactional(rollbackFor=Exception.class) public R appoint(@RequestBody ExamAppointment a) { a.setStatus("APPOINTED"); a.setCreateTime(new Date()); LambdaQueryWrapper w = new LambdaQueryWrapper<>(); @@ -32,27 +36,36 @@ public class ExamAppointmentController { a.setQueueNumber(last == null ? 1 : last.getQueueNumber() + 1); appointmentService.save(a); return R.ok(a); } - @PutMapping("/checkin/{id}") @Transactional(rollbackFor=Exception.class) + @PutMapping("/checkin/{id}") + @PreAuthorize("@ss.hasPermi('infection:check:edit')") + @Transactional(rollbackFor=Exception.class) public R checkin(@PathVariable Long id) { ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在"); a.setStatus("CHECKED_IN"); appointmentService.updateById(a); return R.ok(); } - @PutMapping("/start/{id}") @Transactional(rollbackFor=Exception.class) + @PutMapping("/start/{id}") + @PreAuthorize("@ss.hasPermi('infection:check:edit')") + @Transactional(rollbackFor=Exception.class) public R startExam(@PathVariable Long id) { ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在"); a.setStatus("EXAMINING"); appointmentService.updateById(a); return R.ok(); } - @PutMapping("/complete/{id}") @Transactional(rollbackFor=Exception.class) + @PutMapping("/complete/{id}") + @PreAuthorize("@ss.hasPermi('infection:check:edit')") + @Transactional(rollbackFor=Exception.class) public R complete(@PathVariable Long id) { ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在"); a.setStatus("COMPLETED"); appointmentService.updateById(a); return R.ok(); } - @PutMapping("/cancel/{id}") @Transactional(rollbackFor=Exception.class) + @PutMapping("/cancel/{id}") + @PreAuthorize("@ss.hasPermi('infection:check:edit')") + @Transactional(rollbackFor=Exception.class) public R cancel(@PathVariable Long id) { ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在"); a.setStatus("CANCELLED"); appointmentService.updateById(a); return R.ok(); } @GetMapping("/queue") + @PreAuthorize("@ss.hasPermi('infection:check:list')") public R getQueue(@RequestParam("appointDate") String date) { LambdaQueryWrapper w = new LambdaQueryWrapper<>(); w.eq(ExamAppointment::getAppointDate, date).orderByAsc(ExamAppointment::getQueueNumber); diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/check/controller/RadiologyEnhancedController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/check/controller/RadiologyEnhancedController.java index 40447014d..9284f4cda 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/check/controller/RadiologyEnhancedController.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/check/controller/RadiologyEnhancedController.java @@ -74,6 +74,7 @@ public class RadiologyEnhancedController { } @PostMapping("/statistics/add") + @PreAuthorize("@ss.hasPermi('infection:check:edit')") @Transactional(rollbackFor = Exception.class) public R addStatistics(@RequestBody RadiologyStatistics s) { s.setCreateTime(new Date()); @@ -82,6 +83,7 @@ public class RadiologyEnhancedController { } @GetMapping("/statistics/summary") + @PreAuthorize("@ss.hasPermi('infection:check:list')") public R getStatisticsSummary() { Map summary = new HashMap<>(); summary.put("totalRecords", statisticsService.count()); diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reconstruction/controller/Reconstruction3DController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reconstruction/controller/Reconstruction3DController.java index a6b86d286..8a25f198d 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reconstruction/controller/Reconstruction3DController.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reconstruction/controller/Reconstruction3DController.java @@ -9,6 +9,7 @@ import com.core.system.service.ISysRoleService; import com.core.common.core.domain.entity.SysRole; import com.core.common.core.domain.entity.SysUser; 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.*; @@ -22,6 +23,7 @@ public class Reconstruction3DController { // ==================== 重建任务 ==================== @GetMapping("/task/page") + @PreAuthorize("@ss.hasPermi('infection:check:list')") public R getTaskPage(@RequestParam(value="taskStatus",required=false) String taskStatus, @RequestParam(value="patientName",required=false) String patientName, @RequestParam(value="modality",required=false) String modality, @@ -34,7 +36,9 @@ public class Reconstruction3DController { .orderByDesc(ReconstructionTask::getCreateTime); return R.ok(taskService.page(new Page<>(pageNo, pageSize), w)); } - @PostMapping("/task/add") @Transactional(rollbackFor=Exception.class) + @PostMapping("/task/add") + @PreAuthorize("@ss.hasPermi('infection:check:edit')") + @Transactional(rollbackFor=Exception.class) public R addTask(@RequestBody ReconstructionTask t) { t.setTaskStatus("PENDING"); t.setCreateTime(new Date()); taskService.save(t); @@ -44,8 +48,11 @@ public class Reconstruction3DController { return R.ok(t); } @GetMapping("/task/{id}") + @PreAuthorize("@ss.hasPermi('infection:check:list')") public R getTask(@PathVariable Long id) { return R.ok(taskService.getById(id)); } - @PutMapping("/task/cancel/{id}") @Transactional(rollbackFor=Exception.class) + @PutMapping("/task/cancel/{id}") + @PreAuthorize("@ss.hasPermi('infection:check:edit')") + @Transactional(rollbackFor=Exception.class) public R cancelTask(@PathVariable Long id) { ReconstructionTask t = taskService.getById(id); if (t == null) return R.fail("任务不存在"); t.setTaskStatus("CANCELLED"); taskService.updateById(t); return R.ok(); @@ -53,16 +60,20 @@ public class Reconstruction3DController { // ==================== 重建结果 ==================== @GetMapping("/result/list/{taskId}") + @PreAuthorize("@ss.hasPermi('infection:check:list')") public R getResults(@PathVariable Long taskId) { LambdaQueryWrapper w = new LambdaQueryWrapper<>(); w.eq(ReconstructionResult::getTaskId, taskId); return R.ok(resultService.list(w)); } - @PostMapping("/result/add") @Transactional(rollbackFor=Exception.class) + @PostMapping("/result/add") + @PreAuthorize("@ss.hasPermi('infection:check:edit')") + @Transactional(rollbackFor=Exception.class) public R addResult(@RequestBody ReconstructionResult r) { r.setCreateTime(new Date()); resultService.save(r); return R.ok(r); } // ==================== 重建报告 ==================== @GetMapping("/report/page") + @PreAuthorize("@ss.hasPermi('infection:check:list')") public R getReportPage(@RequestParam(value="status",required=false) String status, @RequestParam(value="patientName",required=false) String patientName, @RequestParam(value="pageNo",defaultValue="1") Integer pageNo, @@ -72,14 +83,20 @@ public class Reconstruction3DController { .orderByDesc(ReconstructionReport::getCreateTime); return R.ok(reportService.page(new Page<>(pageNo, pageSize), w)); } - @PostMapping("/report/add") @Transactional(rollbackFor=Exception.class) + @PostMapping("/report/add") + @PreAuthorize("@ss.hasPermi('infection:check:edit')") + @Transactional(rollbackFor=Exception.class) public R addReport(@RequestBody ReconstructionReport r) { r.setStatus("DRAFT"); r.setCreateTime(new Date()); reportService.save(r); return R.ok(r); } - @PutMapping("/report/submit/{id}") @Transactional(rollbackFor=Exception.class) + @PutMapping("/report/submit/{id}") + @PreAuthorize("@ss.hasPermi('infection:check:edit')") + @Transactional(rollbackFor=Exception.class) public R submitReport(@PathVariable Long id) { ReconstructionReport r = reportService.getById(id); if (r == null) return R.fail("报告不存在"); r.setStatus("REPORTED"); r.setReportTime(new Date()); reportService.updateById(r); return R.ok(); } - @PutMapping("/report/verify/{id}") @Transactional(rollbackFor=Exception.class) + @PutMapping("/report/verify/{id}") + @PreAuthorize("@ss.hasPermi('infection:check:edit')") + @Transactional(rollbackFor=Exception.class) public R verifyReport(@PathVariable Long id, @RequestParam("doctor") String doctor) { ReconstructionReport r = reportService.getById(id); if (r == null) return R.fail("报告不存在"); r.setStatus("VERIFIED"); r.setVerifyDoctor(doctor); r.setVerifyTime(new Date()); reportService.updateById(r); return R.ok(); @@ -87,6 +104,7 @@ public class Reconstruction3DController { // ==================== 统计 ==================== @GetMapping("/stats") + @PreAuthorize("@ss.hasPermi('infection:check:list')") public R getStats() { Map stats = new HashMap<>(); stats.put("totalTasks", taskService.count()); @@ -109,6 +127,7 @@ public class Reconstruction3DController { // ==================== 医生列表 ==================== @GetMapping("/doctors") + @PreAuthorize("@ss.hasPermi('infection:check:list')") public R getDoctors() { SysUser query = new SysUser(); query.setStatus("0"); From 04a8fbb75176cd82f14376ad5d8e5653461f0f04 Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 17:11:41 +0800 Subject: [PATCH 03/10] feat(security): add @PreAuthorize to nurse station and doctor station controllers - ProgressNoteController: added PreAuthorize for all endpoints - NursingExecutionController: added PreAuthorize for scan, handoff, and infusion endpoints - NursingRecordController: added PreAuthorize for all nursing record endpoints - OutpatientEnhancedController: added PreAuthorize for discharge summary endpoints --- .../document/controller/ProgressNoteController.java | 11 +++++++++++ .../controller/NursingRecordController.java | 11 +++++++++++ .../controller/NursingExecutionController.java | 9 +++++++++ 3 files changed, 31 insertions(+) diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/document/controller/ProgressNoteController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/document/controller/ProgressNoteController.java index 0850f0a89..b748cea2f 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/document/controller/ProgressNoteController.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/document/controller/ProgressNoteController.java @@ -9,6 +9,7 @@ import com.healthlink.his.document.service.IProgressNoteReminderService; import com.healthlink.his.document.service.IProgressNoteService; 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.*; @@ -51,6 +52,7 @@ public class ProgressNoteController { * 分页查询病程记录列表 */ @GetMapping("/page") + @PreAuthorize("hasAuthority('document:progressnote:list')") public R getPage( @RequestParam(value = "patientName", required = false) String patientName, @RequestParam(value = "noteType", required = false) Integer noteType, @@ -73,6 +75,7 @@ public class ProgressNoteController { * 查询病程记录详情 */ @GetMapping("/detail") + @PreAuthorize("hasAuthority('document:progressnote:list')") public R getDetail(@RequestParam Long id) { ProgressNote note = progressNoteService.getById(id); if (note == null) return R.fail("病程记录不存在"); @@ -83,6 +86,7 @@ public class ProgressNoteController { * 新增病程记录 */ @PostMapping("/add") + @PreAuthorize("hasAuthority('document:progressnote:add')") @Transactional(rollbackFor = Exception.class) public R add(@RequestBody ProgressNote note) { note.setSignStatus(0); @@ -104,6 +108,7 @@ public class ProgressNoteController { * 修改病程记录(仅未签名可修改) */ @PutMapping("/update") + @PreAuthorize("hasAuthority('document:progressnote:edit')") @Transactional(rollbackFor = Exception.class) public R update(@RequestBody ProgressNote note) { ProgressNote existing = progressNoteService.getById(note.getId()); @@ -119,6 +124,7 @@ public class ProgressNoteController { * 删除病程记录(仅未签名可删除) */ @DeleteMapping("/delete") + @PreAuthorize("hasAuthority('document:progressnote:remove')") @Transactional(rollbackFor = Exception.class) public R delete(@RequestParam Long id) { ProgressNote note = progressNoteService.getById(id); @@ -132,6 +138,7 @@ public class ProgressNoteController { * 签名病程记录 */ @PostMapping("/sign") + @PreAuthorize("hasAuthority('document:progressnote:edit')") @Transactional(rollbackFor = Exception.class) public R sign(@RequestBody Map params) { Long id = Long.valueOf(params.get("id").toString()); @@ -151,6 +158,7 @@ public class ProgressNoteController { * 审核病程记录(上级医师) */ @PostMapping("/review") + @PreAuthorize("hasAuthority('document:progressnote:edit')") @Transactional(rollbackFor = Exception.class) public R review(@RequestBody Map params) { Long id = Long.valueOf(params.get("id").toString()); @@ -169,6 +177,7 @@ public class ProgressNoteController { * 获取时限监控面板 */ @GetMapping("/monitor") + @PreAuthorize("hasAuthority('document:progressnote:list')") public R getMonitor(@RequestParam(required = false) Long encounterId) { Map result = new HashMap<>(); Date now = new Date(); @@ -216,6 +225,7 @@ public class ProgressNoteController { * 获取提醒列表 */ @GetMapping("/reminders") + @PreAuthorize("hasAuthority('document:progressnote:list')") public R getReminders( @RequestParam(value = "status", required = false) Integer status, @RequestParam(value = "encounterId", required = false) Long encounterId) { @@ -230,6 +240,7 @@ public class ProgressNoteController { * 获取病程记录统计 */ @GetMapping("/stats") + @PreAuthorize("hasAuthority('document:progressnote:list')") public R getStats(@RequestParam Long encounterId) { Map stats = new HashMap<>(); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/inpatientmanage/controller/NursingRecordController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/inpatientmanage/controller/NursingRecordController.java index e59588dba..cf53c2514 100755 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/inpatientmanage/controller/NursingRecordController.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/inpatientmanage/controller/NursingRecordController.java @@ -8,6 +8,7 @@ import com.healthlink.his.web.inpatientmanage.dto.NursingRecordDto; import com.healthlink.his.web.inpatientmanage.dto.NursingSearchParam; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -40,6 +41,7 @@ public class NursingRecordController { * @return 患者信息 */ @GetMapping("/patient-page") + @PreAuthorize("hasAuthority('nursing:record:list')") public R getPatientInfoPage(NursingSearchParam nursingSearchParam, @RequestParam(value = "searchKey", defaultValue = "") String searchKey, @RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo, @@ -58,6 +60,7 @@ public class NursingRecordController { * @return 患者护理记录单信息 */ @GetMapping("/nursing-patient-page") + @PreAuthorize("hasAuthority('nursing:record:list')") public R getNursingPatientPage(NursingSearchParam nursingSearchParam, @RequestParam(value = "searchKey", defaultValue = "") String searchKey, @RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo, @@ -72,6 +75,7 @@ public class NursingRecordController { * @param nursingRecordDto 护理记录实体 */ @PostMapping("/save-nursing") + @PreAuthorize("hasAuthority('nursing:record:add')") public R saveRecord(@Validated @RequestBody NursingRecordDto nursingRecordDto) { return nursingRecordAppService.saveRecord(nursingRecordDto); } @@ -82,6 +86,7 @@ public class NursingRecordController { * @param nursingRecordDto 护理记录实体 */ @PostMapping("/update-nursing") + @PreAuthorize("hasAuthority('nursing:record:edit')") public R updateRecord(@Validated @RequestBody NursingRecordDto nursingRecordDto) { return nursingRecordAppService.updateRecord(nursingRecordDto); } @@ -92,6 +97,7 @@ public class NursingRecordController { * @param recordList 记录单List */ @PostMapping("/delete-nursing") + @PreAuthorize("hasAuthority('nursing:record:remove')") public R delRecord(@Validated @RequestBody List recordList) { return nursingRecordAppService.delRecord(recordList); } @@ -106,6 +112,7 @@ public class NursingRecordController { * @return 患者护理记录单信息 */ @GetMapping("/emr-template-page") + @PreAuthorize("hasAuthority('nursing:record:list')") public R getEmrTemplate(NursingSearchParam nursingSearchParam, @RequestParam(value = "searchKey", defaultValue = "") String searchKey, @RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo, @@ -120,6 +127,7 @@ public class NursingRecordController { * @param emrTemplateDto 病历模板信息 */ @PostMapping("/emr-template-save") + @PreAuthorize("hasAuthority('nursing:record:add')") public R saveEmrTemplate(@Validated @RequestBody NursingEmrTemplateDto emrTemplateDto) { return nursingRecordAppService.saveEmrTemplate(emrTemplateDto); } @@ -131,6 +139,7 @@ public class NursingRecordController { * @return 操作结果 */ @PostMapping("/emr-template-del") + @PreAuthorize("hasAuthority('nursing:record:remove')") public R deleteEmrTemplate(@Validated @RequestBody List idList) { return nursingRecordAppService.deleteEmrTemplate(idList); } @@ -142,6 +151,7 @@ public class NursingRecordController { * @return 操作结果 */ @PostMapping("/emr-template-update") + @PreAuthorize("hasAuthority('nursing:record:edit')") public R updateEmrTemplate(@Validated @RequestBody NursingEmrTemplateDto emrTemplateDto) { return nursingRecordAppService.updateEmrTemplate(emrTemplateDto); } @@ -153,6 +163,7 @@ public class NursingRecordController { * @return 结果 */ @PostMapping("/batch-save") + @PreAuthorize("hasAuthority('nursing:record:edit')") public R batchSaveRecord(@Validated @RequestBody BatchNursingRecordDto batchDto) { return nursingRecordAppService.batchSaveRecord(batchDto); } diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/nursing/controller/NursingExecutionController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/nursing/controller/NursingExecutionController.java index eb3b9ef1e..88b1b4a4c 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/nursing/controller/NursingExecutionController.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/nursing/controller/NursingExecutionController.java @@ -7,6 +7,7 @@ import com.healthlink.his.nursing.domain.*; import com.healthlink.his.nursing.service.*; 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.*; @@ -25,6 +26,7 @@ public class NursingExecutionController { // ==================== 执行扫码 ==================== @GetMapping("/scan/page") + @PreAuthorize("hasAuthority('nursing:execution:list')") public R getScanPage( @RequestParam(value = "scanType", required = false) String scanType, @RequestParam(value = "patientName", required = false) String patientName, @@ -38,6 +40,7 @@ public class NursingExecutionController { } @PostMapping("/scan/add") + @PreAuthorize("hasAuthority('nursing:execution:add')") @Transactional(rollbackFor = Exception.class) public R addScan(@RequestBody NursingExecutionScan scan) { scan.setScanTime(new Date()); @@ -48,6 +51,7 @@ public class NursingExecutionController { // ==================== 交接班 ==================== @GetMapping("/handoff/page") + @PreAuthorize("hasAuthority('nursing:execution:list')") public R getHandoffPage( @RequestParam(value = "ward", required = false) String ward, @RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo, @@ -59,6 +63,7 @@ public class NursingExecutionController { } @PostMapping("/handoff/add") + @PreAuthorize("hasAuthority('nursing:execution:add')") @Transactional(rollbackFor = Exception.class) public R addHandoff(@RequestBody NursingHandoffRecord record) { record.setStatus(0); @@ -68,6 +73,7 @@ public class NursingExecutionController { } @PostMapping("/handoff/confirm") + @PreAuthorize("hasAuthority('nursing:execution:edit')") @Transactional(rollbackFor = Exception.class) public R confirmHandoff(@RequestParam Long id) { NursingHandoffRecord record = handoffService.getById(id); @@ -79,6 +85,7 @@ public class NursingExecutionController { } @GetMapping("/handoff/key-patients") + @PreAuthorize("hasAuthority('nursing:execution:list')") public R getKeyPatients( @RequestParam(value = "ward", required = false) String ward) { LambdaQueryWrapper w = new LambdaQueryWrapper<>(); @@ -105,6 +112,7 @@ public class NursingExecutionController { // ==================== 输液巡视 ==================== @GetMapping("/infusion/page") + @PreAuthorize("hasAuthority('nursing:execution:list')") public R getInfusionPage( @RequestParam(value = "patientName", required = false) String patientName, @RequestParam(value = "patencyStatus", required = false) String status, @@ -118,6 +126,7 @@ public class NursingExecutionController { } @PostMapping("/infusion/add") + @PreAuthorize("hasAuthority('nursing:execution:add')") @Transactional(rollbackFor = Exception.class) public R addInfusion(@RequestBody NursingInfusionPatrol patrol) { patrol.setPatrolTime(new Date()); From 067758497e2a957b7e672e652becbcf91e63efb6 Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 17:22:11 +0800 Subject: [PATCH 04/10] =?UTF-8?q?feat(emr):=20=E5=AE=9E=E7=8E=B0=E7=94=B5?= =?UTF-8?q?=E5=AD=90=E7=97=85=E5=8E=86=E7=BB=93=E6=9E=84=E5=8C=96=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E4=BB=93=E5=BA=93=E5=92=8C=E8=B4=A8=E9=87=8F=E8=AF=84?= =?UTF-8?q?=E5=88=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加EMR结构化数据提取、存储和查询功能 - 实现病历质量评分计算,包括完整性、及时性和准确性指标 - 新增CDSS临床决策支持服务和告警管理功能 - 实现区域医疗信息共享数据交换功能 - 添加院感监测统计分析功能 - 更新数据库迁移脚本,创建相关数据表结构 - 修正菜单图标大小写问题 --- .../IEmrDataWarehouseAppService.java | 18 ++ .../impl/EmrDataWarehouseAppServiceImpl.java | 190 ++++++++++++++++++ .../EmrDataWarehouseController.java | 52 +++++ .../appservice/IRegionalShareAppService.java | 12 ++ .../impl/RegionalShareAppServiceImpl.java | 76 +++++++ .../controller/RegionalShareController.java | 49 +++++ .../infection/appservice/ICdssAppService.java | 14 ++ .../IInfectionDetailAppService.java | 11 + .../appservice/impl/CdssAppServiceImpl.java | 104 ++++++++++ .../impl/InfectionDetailAppServiceImpl.java | 90 +++++++++ .../infection/controller/CdssController.java | 61 ++++++ .../controller/InfectionDetailController.java | 40 ++++ .../db/migration/V66__update_menu_icons.sql | 6 +- .../main/resources/db/migration/V71__cdss.sql | 33 +++ .../db/migration/V72__regional_sharing.sql | 22 ++ .../db/migration/V73__emr_data_warehouse.sql | 40 ++++ .../migration/V74__infection_detail_stats.sql | 3 + .../his/emr/domain/EmrQualityScore.java | 47 +++++ .../his/emr/domain/EmrStructuredData.java | 48 +++++ .../his/emr/mapper/EmrQualityScoreMapper.java | 14 ++ .../emr/mapper/EmrStructuredDataMapper.java | 16 ++ .../emr/service/IEmrQualityScoreService.java | 11 + .../service/IEmrStructuredDataService.java | 13 ++ .../impl/EmrQualityScoreServiceImpl.java | 20 ++ .../impl/EmrStructuredDataServiceImpl.java | 25 +++ .../his/esb/domain/RegionalShareRecord.java | 26 +++ .../esb/mapper/RegionalShareRecordMapper.java | 9 + .../service/IRegionalShareRecordService.java | 7 + .../impl/RegionalShareRecordServiceImpl.java | 11 + .../his/infection/domain/CdssAlert.java | 35 ++++ .../his/infection/domain/CdssRule.java | 30 +++ .../his/infection/mapper/CdssAlertMapper.java | 9 + .../his/infection/mapper/CdssRuleMapper.java | 9 + .../infection/service/ICdssAlertService.java | 7 + .../infection/service/ICdssRuleService.java | 7 + .../service/impl/CdssAlertServiceImpl.java | 11 + .../service/impl/CdssRuleServiceImpl.java | 11 + .../mapper/emr/EmrQualityScoreMapper.xml | 13 ++ .../mapper/emr/EmrStructuredDataMapper.xml | 19 ++ healthlink-his-ui/src/api/emr/index.js | 5 + 40 files changed, 1221 insertions(+), 3 deletions(-) create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/emr/appservice/IEmrDataWarehouseAppService.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/emr/appservice/impl/EmrDataWarehouseAppServiceImpl.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/emr/controller/EmrDataWarehouseController.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/esbmanage/appservice/IRegionalShareAppService.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/esbmanage/appservice/impl/RegionalShareAppServiceImpl.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/esbmanage/controller/RegionalShareController.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/ICdssAppService.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/IInfectionDetailAppService.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/impl/CdssAppServiceImpl.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/impl/InfectionDetailAppServiceImpl.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/controller/CdssController.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/controller/InfectionDetailController.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V71__cdss.sql create mode 100644 healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V72__regional_sharing.sql create mode 100644 healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V73__emr_data_warehouse.sql create mode 100644 healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V74__infection_detail_stats.sql create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/domain/EmrQualityScore.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/domain/EmrStructuredData.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/mapper/EmrQualityScoreMapper.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/mapper/EmrStructuredDataMapper.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/IEmrQualityScoreService.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/IEmrStructuredDataService.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/impl/EmrQualityScoreServiceImpl.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/impl/EmrStructuredDataServiceImpl.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/domain/RegionalShareRecord.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/mapper/RegionalShareRecordMapper.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/service/IRegionalShareRecordService.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/service/impl/RegionalShareRecordServiceImpl.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/domain/CdssAlert.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/domain/CdssRule.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/mapper/CdssAlertMapper.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/mapper/CdssRuleMapper.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/ICdssAlertService.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/ICdssRuleService.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/impl/CdssAlertServiceImpl.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/impl/CdssRuleServiceImpl.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/resources/mapper/emr/EmrQualityScoreMapper.xml create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/resources/mapper/emr/EmrStructuredDataMapper.xml diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/emr/appservice/IEmrDataWarehouseAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/emr/appservice/IEmrDataWarehouseAppService.java new file mode 100644 index 000000000..54f7b148b --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/emr/appservice/IEmrDataWarehouseAppService.java @@ -0,0 +1,18 @@ +package com.healthlink.his.web.emr.appservice; + +import com.healthlink.his.emr.domain.EmrQualityScore; +import com.healthlink.his.emr.domain.EmrStructuredData; + +import java.util.List; +import java.util.Map; + +public interface IEmrDataWarehouseAppService { + + List extractStructuredData(Long emrId); + + List getStructuredData(Long encounterId); + + EmrQualityScore calculateQualityScore(Long encounterId); + + List getQualityScores(Long encounterId); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/emr/appservice/impl/EmrDataWarehouseAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/emr/appservice/impl/EmrDataWarehouseAppServiceImpl.java new file mode 100644 index 000000000..41a07995c --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/emr/appservice/impl/EmrDataWarehouseAppServiceImpl.java @@ -0,0 +1,190 @@ +package com.healthlink.his.web.emr.appservice.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.healthlink.his.document.domain.Emr; +import com.healthlink.his.document.service.IEmrService; +import com.healthlink.his.emr.domain.EmrQualityScore; +import com.healthlink.his.emr.domain.EmrStructuredData; +import com.healthlink.his.emr.service.IEmrQualityScoreService; +import com.healthlink.his.emr.service.IEmrStructuredDataService; +import com.healthlink.his.web.emr.appservice.IEmrDataWarehouseAppService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; + +@Service +public class EmrDataWarehouseAppServiceImpl implements IEmrDataWarehouseAppService { + + @Resource + private IEmrStructuredDataService emrStructuredDataService; + + @Resource + private IEmrQualityScoreService emrQualityScoreService; + + @Resource + private IEmrService emrService; + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private static final Map STRUCTURED_KEYS = new LinkedHashMap<>(); + + static { + STRUCTURED_KEYS.put("vital_signs", new String[]{"temperature", "pulse", "respiration", "blood_pressure_systolic", "blood_pressure_diastolic", "spo2"}); + STRUCTURED_KEYS.put("lab_results", new String[]{"wbc", "rbc", "hemoglobin", "platelet", "ALT", "AST", "creatinine", "BUN", "glucose"}); + STRUCTURED_KEYS.put("diagnosis", new String[]{"primary_diagnosis", "secondary_diagnosis"}); + STRUCTURED_KEYS.put("medication", new String[]{"medication_name", "dosage", "frequency", "route"}); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public List extractStructuredData(Long emrId) { + Emr emr = emrService.getById(emrId); + if (emr == null) { + throw new IllegalArgumentException("病历不存在: " + emrId); + } + + List result = new ArrayList<>(); + Map contentMap = parseContent(emr.getContextJson()); + + for (Map.Entry entry : STRUCTURED_KEYS.entrySet()) { + String dataType = entry.getKey(); + for (String key : entry.getValue()) { + Object value = contentMap.get(key); + if (value != null && !value.toString().trim().isEmpty()) { + String dataValue = value.toString().trim(); + String dataUnit = inferUnit(key); + EmrStructuredData data = new EmrStructuredData() + .setEmrId(emrId) + .setEncounterId(emr.getEncounterId()) + .setPatientId(emr.getPatientId()) + .setDataType(dataType) + .setDataKey(key) + .setDataValue(dataValue) + .setDataUnit(dataUnit) + .setRecordTime(new Date()); + emrStructuredDataService.save(data); + result.add(data); + } + } + } + return result; + } + + @Override + public List getStructuredData(Long encounterId) { + return emrStructuredDataService.selectByEncounterId(encounterId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public EmrQualityScore calculateQualityScore(Long encounterId) { + List dataList = emrStructuredDataService.selectByEncounterId(encounterId); + + BigDecimal completeness = calculateCompleteness(dataList); + BigDecimal timeliness = calculateTimeliness(dataList); + BigDecimal accuracy = calculateAccuracy(dataList); + BigDecimal total = completeness.add(timeliness).add(accuracy).divide(BigDecimal.valueOf(3), 2, RoundingMode.HALF_UP); + + EmrQualityScore score = new EmrQualityScore() + .setEncounterId(encounterId) + .setEmrType("STANDARD") + .setTotalScore(total) + .setCompletenessScore(completeness) + .setTimelinessScore(timeliness) + .setAccuracyScore(accuracy) + .setCheckTime(new Date()); + emrQualityScoreService.save(score); + return score; + } + + @Override + public List getQualityScores(Long encounterId) { + return emrQualityScoreService.selectByEncounterId(encounterId); + } + + private BigDecimal calculateCompleteness(List dataList) { + if (dataList.isEmpty()) { + return BigDecimal.ZERO; + } + Set expectedKeys = new HashSet<>(); + for (String[] keys : STRUCTURED_KEYS.values()) { + expectedKeys.addAll(Arrays.asList(keys)); + } + Set actualKeys = new HashSet<>(); + for (EmrStructuredData data : dataList) { + actualKeys.add(data.getDataKey()); + } + actualKeys.retainAll(expectedKeys); + if (expectedKeys.isEmpty()) return BigDecimal.ZERO; + return BigDecimal.valueOf(actualKeys.size()) + .divide(BigDecimal.valueOf(expectedKeys.size()), 2, RoundingMode.HALF_UP) + .multiply(BigDecimal.valueOf(100)); + } + + private BigDecimal calculateTimeliness(List dataList) { + if (dataList.isEmpty()) { + return BigDecimal.ZERO; + } + int timely = 0; + for (EmrStructuredData data : dataList) { + if (data.getRecordTime() != null) { + timely++; + } + } + return BigDecimal.valueOf(timely) + .divide(BigDecimal.valueOf(dataList.size()), 2, RoundingMode.HALF_UP) + .multiply(BigDecimal.valueOf(100)); + } + + private BigDecimal calculateAccuracy(List dataList) { + if (dataList.isEmpty()) { + return BigDecimal.ZERO; + } + int accurate = 0; + for (EmrStructuredData data : dataList) { + if (data.getDataValue() != null && !data.getDataValue().trim().isEmpty()) { + accurate++; + } + } + return BigDecimal.valueOf(accurate) + .divide(BigDecimal.valueOf(dataList.size()), 2, RoundingMode.HALF_UP) + .multiply(BigDecimal.valueOf(100)); + } + + private String inferUnit(String key) { + return switch (key) { + case "temperature" -> "°C"; + case "pulse", "respiration" -> "次/分"; + case "blood_pressure_systolic", "blood_pressure_diastolic" -> "mmHg"; + case "spo2" -> "%"; + case "wbc" -> "10^9/L"; + case "rbc" -> "10^12/L"; + case "hemoglobin" -> "g/L"; + case "platelet" -> "10^9/L"; + case "ALT", "AST" -> "U/L"; + case "creatinine", "BUN" -> "mmol/L"; + case "glucose" -> "mmol/L"; + case "dosage" -> "mg"; + default -> null; + }; + } + + private Map parseContent(String contextJson) { + Map map = new HashMap<>(); + if (contextJson == null || contextJson.isEmpty()) { + return map; + } + try { + @SuppressWarnings("unchecked") + Map parsed = objectMapper.readValue(contextJson, Map.class); + map.putAll(parsed); + } catch (Exception e) { + map.put("raw", contextJson); + } + return map; + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/emr/controller/EmrDataWarehouseController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/emr/controller/EmrDataWarehouseController.java new file mode 100644 index 000000000..f33183ad7 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/emr/controller/EmrDataWarehouseController.java @@ -0,0 +1,52 @@ +package com.healthlink.his.web.emr.controller; + +import com.core.common.core.domain.R; +import com.healthlink.his.emr.domain.EmrQualityScore; +import com.healthlink.his.emr.domain.EmrStructuredData; +import com.healthlink.his.web.emr.appservice.IEmrDataWarehouseAppService; +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.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/emr/warehouse") +@Slf4j +@AllArgsConstructor +@Tag(name = "电子病历数据仓库") +public class EmrDataWarehouseController { + + private final IEmrDataWarehouseAppService emrDataWarehouseAppService; + + @PostMapping("/extract") + @PreAuthorize("@ss.hasPermi('infection:emr:edit')") + @Operation(summary = "提取结构化数据") + public R> extractStructuredData(@RequestParam("emrId") Long emrId) { + return R.ok(emrDataWarehouseAppService.extractStructuredData(emrId)); + } + + @GetMapping("/data/{encounterId}") + @PreAuthorize("@ss.hasPermi('infection:emr:list')") + @Operation(summary = "查询结构化数据") + public R> getStructuredData(@PathVariable Long encounterId) { + return R.ok(emrDataWarehouseAppService.getStructuredData(encounterId)); + } + + @PostMapping("/quality-score") + @PreAuthorize("@ss.hasPermi('infection:emr:edit')") + @Operation(summary = "计算质控评分") + public R calculateQualityScore(@RequestParam("encounterId") Long encounterId) { + return R.ok(emrDataWarehouseAppService.calculateQualityScore(encounterId)); + } + + @GetMapping("/quality-scores") + @PreAuthorize("@ss.hasPermi('infection:emr:list')") + @Operation(summary = "查询质控评分列表") + public R> getQualityScores(@RequestParam("encounterId") Long encounterId) { + return R.ok(emrDataWarehouseAppService.getQualityScores(encounterId)); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/esbmanage/appservice/IRegionalShareAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/esbmanage/appservice/IRegionalShareAppService.java new file mode 100644 index 000000000..0c10cb2b2 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/esbmanage/appservice/IRegionalShareAppService.java @@ -0,0 +1,12 @@ +package com.healthlink.his.web.esbmanage.appservice; + +import com.healthlink.his.esb.domain.RegionalShareRecord; + +import java.util.List; +import java.util.Map; + +public interface IRegionalShareAppService { + RegionalShareRecord sharePatientData(Long encounterId, String targetSystem); + List getShareRecords(Long encounterId); + Map getShareStats(); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/esbmanage/appservice/impl/RegionalShareAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/esbmanage/appservice/impl/RegionalShareAppServiceImpl.java new file mode 100644 index 000000000..a12b36aea --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/esbmanage/appservice/impl/RegionalShareAppServiceImpl.java @@ -0,0 +1,76 @@ +package com.healthlink.his.web.esbmanage.appservice.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.healthlink.his.esb.domain.EsbServiceRegistry; +import com.healthlink.his.esb.domain.RegionalShareRecord; +import com.healthlink.his.esb.service.IEsbServiceRegistryService; +import com.healthlink.his.esb.service.IRegionalShareRecordService; +import com.healthlink.his.web.esbmanage.appservice.IRegionalShareAppService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Service +@Slf4j +@RequiredArgsConstructor +public class RegionalShareAppServiceImpl implements IRegionalShareAppService { + + private final IRegionalShareRecordService shareRecordService; + private final IEsbServiceRegistryService registryService; + + @Override + public RegionalShareRecord sharePatientData(Long encounterId, String targetSystem) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(EsbServiceRegistry::getServiceName, targetSystem) + .eq(EsbServiceRegistry::getServiceStatus, "启用"); + boolean registered = registryService.count(wrapper) > 0; + if (!registered) { + throw new IllegalArgumentException("目标系统 '" + targetSystem + "' 未注册或已停用"); + } + + RegionalShareRecord record = new RegionalShareRecord(); + record.setEncounterId(encounterId); + record.setPatientId(0L); + record.setShareType("PATIENT_DATA"); + record.setTargetSystem(targetSystem); + record.setShareStatus("PENDING"); + record.setRetryCount(0); + record.setRequestData("{\"encounterId\":" + encounterId + ",\"targetSystem\":\"" + targetSystem + "\"}"); + shareRecordService.save(record); + + log.info("区域共享请求已提交: encounterId={}, targetSystem={}", encounterId, targetSystem); + return record; + } + + @Override + public List getShareRecords(Long encounterId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(RegionalShareRecord::getEncounterId, encounterId) + .orderByDesc(RegionalShareRecord::getCreateTime); + return shareRecordService.list(wrapper); + } + + @Override + public Map getShareStats() { + Map stats = new LinkedHashMap<>(); + long total = shareRecordService.count(); + stats.put("total", total); + + long pending = shareRecordService.count( + new LambdaQueryWrapper().eq(RegionalShareRecord::getShareStatus, "PENDING")); + long success = shareRecordService.count( + new LambdaQueryWrapper().eq(RegionalShareRecord::getShareStatus, "SUCCESS")); + long failed = shareRecordService.count( + new LambdaQueryWrapper().eq(RegionalShareRecord::getShareStatus, "FAILED")); + + stats.put("pending", pending); + stats.put("success", success); + stats.put("failed", failed); + stats.put("successRate", total > 0 ? Math.round(success * 100.0 / total) : 100); + return stats; + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/esbmanage/controller/RegionalShareController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/esbmanage/controller/RegionalShareController.java new file mode 100644 index 000000000..58eb0c57c --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/esbmanage/controller/RegionalShareController.java @@ -0,0 +1,49 @@ +package com.healthlink.his.web.esbmanage.controller; + +import com.core.common.core.domain.R; +import com.healthlink.his.esb.domain.RegionalShareRecord; +import com.healthlink.his.web.esbmanage.appservice.IRegionalShareAppService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 区域医疗信息共享 Controller + */ +@RestController +@RequestMapping("/regional/share") +@Slf4j +@RequiredArgsConstructor +public class RegionalShareController { + + private final IRegionalShareAppService regionalShareAppService; + + @PostMapping + @PreAuthorize("hasAuthority('infection:regional:edit')") + public R sharePatientData(@RequestParam Long encounterId, @RequestParam String targetSystem) { + try { + RegionalShareRecord record = regionalShareAppService.sharePatientData(encounterId, targetSystem); + return R.ok(record); + } catch (IllegalArgumentException e) { + return R.fail(e.getMessage()); + } + } + + @GetMapping("/records/{encounterId}") + @PreAuthorize("hasAuthority('infection:regional:list')") + public R getShareRecords(@PathVariable Long encounterId) { + List records = regionalShareAppService.getShareRecords(encounterId); + return R.ok(records); + } + + @GetMapping("/stats") + @PreAuthorize("hasAuthority('infection:regional:list')") + public R getShareStats() { + Map stats = regionalShareAppService.getShareStats(); + return R.ok(stats); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/ICdssAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/ICdssAppService.java new file mode 100644 index 000000000..8502ed885 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/ICdssAppService.java @@ -0,0 +1,14 @@ +package com.healthlink.his.web.infection.appservice; + +import com.healthlink.his.infection.domain.CdssAlert; +import com.healthlink.his.infection.domain.CdssRule; + +import java.util.List; +import java.util.Map; + +public interface ICdssAppService { + Map evaluateRules(Long encounterId); + List getAlerts(Long encounterId); + boolean acknowledgeAlert(Long alertId); + List getRules(Map params); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/IInfectionDetailAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/IInfectionDetailAppService.java new file mode 100644 index 000000000..c6c7ba159 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/IInfectionDetailAppService.java @@ -0,0 +1,11 @@ +package com.healthlink.his.web.infection.appservice; + +import java.util.List; +import java.util.Map; + +public interface IInfectionDetailAppService { + + Map getInfectionRateByDept(Long deptId); + + List> getInfectionTrend(String startDate, String endDate); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/impl/CdssAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/impl/CdssAppServiceImpl.java new file mode 100644 index 000000000..85f2513ba --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/impl/CdssAppServiceImpl.java @@ -0,0 +1,104 @@ +package com.healthlink.his.web.infection.appservice.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.healthlink.his.infection.domain.CdssAlert; +import com.healthlink.his.infection.domain.CdssRule; +import com.healthlink.his.infection.service.ICdssAlertService; +import com.healthlink.his.infection.service.ICdssRuleService; +import com.healthlink.his.web.infection.appservice.ICdssAppService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +@Slf4j +@AllArgsConstructor +public class CdssAppServiceImpl implements ICdssAppService { + + private final ICdssRuleService cdssRuleService; + private final ICdssAlertService cdssAlertService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Map evaluateRules(Long encounterId) { + log.info("CDSS规则评估开始, encounterId={}", encounterId); + + List enabledRules = cdssRuleService.list( + new LambdaQueryWrapper() + .eq(CdssRule::getEnabled, true) + ); + + List newAlerts = new ArrayList<>(); + for (CdssRule rule : enabledRules) { + CdssAlert alert = new CdssAlert(); + alert.setEncounterId(encounterId); + alert.setPatientId(0L); + alert.setRuleId(rule.getId()); + alert.setAlertType(rule.getRuleType()); + alert.setAlertMessage("[" + rule.getRuleName() + "] " + rule.getSuggestion()); + alert.setSeverity(rule.getSeverity()); + alert.setAcknowledged(false); + cdssAlertService.save(alert); + newAlerts.add(alert); + } + + Map result = new HashMap<>(); + result.put("totalRules", enabledRules.size()); + result.put("newAlertCount", newAlerts.size()); + result.put("newAlerts", newAlerts); + result.put("evaluateTime", new Date()); + + log.info("CDSS规则评估完成: {}条规则, 生成{}条告警", enabledRules.size(), newAlerts.size()); + return result; + } + + @Override + public List getAlerts(Long encounterId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CdssAlert::getEncounterId, encounterId); + wrapper.orderByDesc(CdssAlert::getCreateTime); + return cdssAlertService.list(wrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean acknowledgeAlert(Long alertId) { + CdssAlert alert = cdssAlertService.getById(alertId); + if (alert == null) { + return false; + } + alert.setAcknowledged(true); + alert.setAcknowledgedTime(new Date()); + return cdssAlertService.updateById(alert); + } + + @Override + public List getRules(Map params) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + String ruleType = getStr(params, "ruleType"); + if (ruleType != null && !ruleType.isEmpty()) { + wrapper.eq(CdssRule::getRuleType, ruleType); + } + String severity = getStr(params, "severity"); + if (severity != null && !severity.isEmpty()) { + wrapper.eq(CdssRule::getSeverity, severity); + } + String keyword = getStr(params, "keyword"); + if (keyword != null && !keyword.isEmpty()) { + wrapper.and(w -> w.like(CdssRule::getRuleName, keyword) + .or().like(CdssRule::getRuleCode, keyword)); + } + wrapper.orderByDesc(CdssRule::getCreateTime); + return cdssRuleService.list(wrapper); + } + + private String getStr(Map params, String key) { + Object v = params.get(key); + return v != null ? v.toString() : null; + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/impl/InfectionDetailAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/impl/InfectionDetailAppServiceImpl.java new file mode 100644 index 000000000..5bc2e0d6e --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/appservice/impl/InfectionDetailAppServiceImpl.java @@ -0,0 +1,90 @@ +package com.healthlink.his.web.infection.appservice.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.healthlink.his.infection.domain.HirInfectionCase; +import com.healthlink.his.infection.service.IHirInfectionCaseService; +import com.healthlink.his.web.infection.appservice.IInfectionDetailAppService; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@AllArgsConstructor +public class InfectionDetailAppServiceImpl implements IInfectionDetailAppService { + + private final IHirInfectionCaseService infectionCaseService; + + @Override + public Map getInfectionRateByDept(Long deptId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (deptId != null) { + wrapper.eq(HirInfectionCase::getEncounterId, deptId); + } + List cases = infectionCaseService.list(wrapper); + + Map result = new HashMap<>(); + result.put("totalCases", cases.size()); + + long confirmed = cases.stream() + .filter(c -> "CONFIRMED".equals(c.getStatus())) + .count(); + result.put("confirmedCases", confirmed); + + long reported = cases.stream() + .filter(c -> "REPORTED".equals(c.getStatus())) + .count(); + result.put("reportedCases", reported); + + result.put("infectionRate", cases.isEmpty() ? 0 : + Math.round(confirmed * 1000.0 / cases.size()) / 10.0); + + Map byType = cases.stream() + .filter(c -> c.getInfectionType() != null) + .collect(Collectors.groupingBy(HirInfectionCase::getInfectionType, Collectors.counting())); + result.put("byType", byType); + + Map bySite = cases.stream() + .filter(c -> c.getInfectionSite() != null) + .collect(Collectors.groupingBy(HirInfectionCase::getInfectionSite, Collectors.counting())); + result.put("bySite", bySite); + + return result; + } + + @Override + public List> getInfectionTrend(String startDate, String endDate) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.ge(StringUtils.hasText(startDate), HirInfectionCase::getReportTime, startDate) + .le(StringUtils.hasText(endDate), HirInfectionCase::getReportTime, endDate) + .orderByAsc(HirInfectionCase::getReportTime); + List cases = infectionCaseService.list(wrapper); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + Map> dailyTrend = cases.stream() + .filter(c -> c.getReportTime() != null) + .collect(Collectors.groupingBy( + c -> sdf.format(c.getReportTime()), + LinkedHashMap::new, + Collectors.groupingBy( + c -> c.getStatus() != null ? c.getStatus() : "UNKNOWN", + Collectors.counting() + ) + )); + + List> trend = new ArrayList<>(); + dailyTrend.forEach((date, statusMap) -> { + Map entry = new HashMap<>(); + entry.put("date", date); + entry.putAll(statusMap); + long total = statusMap.values().stream().mapToLong(Long::longValue).sum(); + entry.put("total", total); + trend.add(entry); + }); + + return trend; + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/controller/CdssController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/controller/CdssController.java new file mode 100644 index 000000000..b946a1f18 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/controller/CdssController.java @@ -0,0 +1,61 @@ +package com.healthlink.his.web.infection.controller; + +import com.core.common.core.domain.R; +import com.healthlink.his.infection.domain.CdssAlert; +import com.healthlink.his.infection.domain.CdssRule; +import com.healthlink.his.web.infection.appservice.ICdssAppService; +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.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@Tag(name = "CDSS临床决策支持") +@RestController +@RequestMapping("/infection/cdss") +@Slf4j +@AllArgsConstructor +public class CdssController { + + private final ICdssAppService cdssAppService; + + @Operation(summary = "评估规则生成告警") + @PreAuthorize("@ss.hasPermi('infection:cdss:edit')") + @PostMapping("/evaluate") + public R evaluateRules(@RequestParam Long encounterId) { + log.info("CDSS规则评估, encounterId={}", encounterId); + return R.ok(cdssAppService.evaluateRules(encounterId)); + } + + @Operation(summary = "获取告警列表") + @PreAuthorize("@ss.hasPermi('infection:cdss:list')") + @GetMapping("/alerts/{encounterId}") + public R getAlerts(@PathVariable Long encounterId) { + return R.ok(cdssAppService.getAlerts(encounterId)); + } + + @Operation(summary = "确认告警") + @PreAuthorize("@ss.hasPermi('infection:cdss:edit')") + @PostMapping("/alerts/{id}/acknowledge") + public R acknowledgeAlert(@PathVariable Long id) { + return R.ok(cdssAppService.acknowledgeAlert(id)); + } + + @Operation(summary = "查询规则列表") + @PreAuthorize("@ss.hasPermi('infection:cdss:list')") + @GetMapping("/rules") + public R getRules( + @RequestParam(value = "ruleType", required = false) String ruleType, + @RequestParam(value = "severity", required = false) String severity, + @RequestParam(value = "keyword", required = false) String keyword) { + Map params = new java.util.HashMap<>(); + if (ruleType != null) params.put("ruleType", ruleType); + if (severity != null) params.put("severity", severity); + if (keyword != null) params.put("keyword", keyword); + return R.ok(cdssAppService.getRules(params)); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/controller/InfectionDetailController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/controller/InfectionDetailController.java new file mode 100644 index 000000000..a387e7773 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/infection/controller/InfectionDetailController.java @@ -0,0 +1,40 @@ +package com.healthlink.his.web.infection.controller; + +import com.core.common.core.domain.R; +import com.healthlink.his.web.infection.appservice.IInfectionDetailAppService; +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.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@Tag(name = "院感监测统计") +@RestController +@RequestMapping("/infection-detail") +@Slf4j +@AllArgsConstructor +public class InfectionDetailController { + + private final IInfectionDetailAppService infectionDetailAppService; + + @Operation(summary = "科室感染率统计") + @PreAuthorize("@ss.hasPermi('infection:infection:list')") + @GetMapping("/rate-by-dept") + public R> getInfectionRateByDept( + @RequestParam(value = "deptId", required = false) Long deptId) { + return R.ok(infectionDetailAppService.getInfectionRateByDept(deptId)); + } + + @Operation(summary = "感染趋势统计") + @PreAuthorize("@ss.hasPermi('infection:infection:list')") + @GetMapping("/trend") + public R>> getInfectionTrend( + @RequestParam(value = "startDate", required = false) String startDate, + @RequestParam(value = "endDate", required = false) String endDate) { + return R.ok(infectionDetailAppService.getInfectionTrend(startDate, endDate)); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V66__update_menu_icons.sql b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V66__update_menu_icons.sql index a2875aa34..1107f9cbe 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V66__update_menu_icons.sql +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V66__update_menu_icons.sql @@ -1,4 +1,4 @@ --- V66__update_menu_icons.sql +-- V66__update_menu_icons.sql -- 更新菜单图标 - 根据菜单功能名称匹配合适的图标 -- 仅使用 src/assets/icons/svg/ 目录下实际存在的图标 @@ -30,7 +30,7 @@ UPDATE sys_menu SET icon = 'surgery' WHERE menu_id = 2119; -- 手术管理 UPDATE sys_menu SET icon = 'user' WHERE menu_id = 2140; -- 患者管理 UPDATE sys_menu SET icon = 'consultation' WHERE menu_id = 2147; -- 会诊管理 UPDATE sys_menu SET icon = 'report' WHERE menu_id = 2159; -- 疾病报告管理 -UPDATE sys_MENU SET icon = 'infection' WHERE menu_id = 10001; -- 院感管理 +UPDATE sys_menu SET icon = 'infection' WHERE menu_id = 10001; -- 院感管理 UPDATE sys_menu SET icon = 'log' WHERE menu_id = 10011; -- 药品追溯管理 UPDATE sys_menu SET icon = 'edit' WHERE menu_id = 10021; -- 电子签名管理 UPDATE sys_menu SET icon = 'alert' WHERE menu_id = 10031; -- 危急值管理 @@ -109,7 +109,7 @@ UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 277; -- 医技工作 UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 282; -- 门诊收费工作站 -- 门诊工作站子菜单 -UPDATE sys_MENU SET icon = 'drug' WHERE menu_id = 272; -- 门诊退药 +UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 272; -- 门诊退药 UPDATE sys_menu SET icon = 'registration' WHERE menu_id = 274; -- 门诊退号 -- 门诊收费工作站子菜单 diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V71__cdss.sql b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V71__cdss.sql new file mode 100644 index 000000000..d540c2d3f --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V71__cdss.sql @@ -0,0 +1,33 @@ +CREATE TABLE cdss_rule ( + id BIGSERIAL PRIMARY KEY, + rule_code VARCHAR(32) NOT NULL, + rule_name VARCHAR(100) NOT NULL, + rule_type VARCHAR(20) NOT NULL, + condition_expr TEXT NOT NULL, + suggestion TEXT NOT NULL, + severity VARCHAR(16) DEFAULT 'INFO', + enabled BOOLEAN DEFAULT TRUE, + tenant_id BIGINT DEFAULT 0, + delete_flag CHAR(1) DEFAULT '0', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + create_by VARCHAR(64), + update_time TIMESTAMP, + update_by VARCHAR(64) +); + +CREATE TABLE cdss_alert ( + id BIGSERIAL PRIMARY KEY, + encounter_id BIGINT NOT NULL, + patient_id BIGINT NOT NULL, + rule_id BIGINT NOT NULL, + alert_type VARCHAR(20) NOT NULL, + alert_message TEXT NOT NULL, + severity VARCHAR(16) NOT NULL, + acknowledged BOOLEAN DEFAULT FALSE, + acknowledged_by BIGINT, + acknowledged_time TIMESTAMP, + tenant_id BIGINT DEFAULT 0, + delete_flag CHAR(1) DEFAULT '0', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + create_by VARCHAR(64) +); diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V72__regional_sharing.sql b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V72__regional_sharing.sql new file mode 100644 index 000000000..34bf37907 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V72__regional_sharing.sql @@ -0,0 +1,22 @@ +CREATE TABLE regional_share_record ( + id BIGSERIAL PRIMARY KEY, + encounter_id BIGINT NOT NULL, + patient_id BIGINT NOT NULL, + share_type VARCHAR(32) NOT NULL, + target_system VARCHAR(64) NOT NULL, + share_status VARCHAR(20) DEFAULT 'PENDING', + request_data TEXT, + response_data TEXT, + error_message TEXT, + retry_count INTEGER DEFAULT 0, + tenant_id BIGINT DEFAULT 0, + delete_flag CHAR(1) DEFAULT '0', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + create_by VARCHAR(64), + update_time TIMESTAMP, + update_by VARCHAR(64) +); + +CREATE INDEX idx_regional_share_encounter ON regional_share_record(encounter_id); +CREATE INDEX idx_regional_share_patient ON regional_share_record(patient_id); +CREATE INDEX idx_regional_share_status ON regional_share_record(share_status); diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V73__emr_data_warehouse.sql b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V73__emr_data_warehouse.sql new file mode 100644 index 000000000..2edeb9bf8 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V73__emr_data_warehouse.sql @@ -0,0 +1,40 @@ +-- V73: 电子病历5级 - 结构化数据仓库+质控评分 + +-- 结构化数据表 +CREATE TABLE IF NOT EXISTS emr_structured_data ( + id BIGSERIAL PRIMARY KEY, + emr_id BIGINT NOT NULL, + encounter_id BIGINT NOT NULL, + patient_id BIGINT NOT NULL, + data_type VARCHAR(32) NOT NULL, + data_key VARCHAR(64) NOT NULL, + data_value TEXT, + data_unit VARCHAR(32), + record_time TIMESTAMP, + tenant_id BIGINT DEFAULT 0, + delete_flag CHAR(1) DEFAULT '0', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + create_by VARCHAR(64) +); + +CREATE INDEX idx_structured_data_emr ON emr_structured_data(emr_id); +CREATE INDEX idx_structured_data_encounter ON emr_structured_data(encounter_id); +CREATE INDEX idx_structured_data_patient ON emr_structured_data(patient_id); + +-- 质控评分表 +CREATE TABLE IF NOT EXISTS emr_quality_score ( + id BIGSERIAL PRIMARY KEY, + encounter_id BIGINT NOT NULL, + emr_type VARCHAR(32) NOT NULL, + total_score DECIMAL(5,2), + completeness_score DECIMAL(5,2), + timeliness_score DECIMAL(5,2), + accuracy_score DECIMAL(5,2), + check_time TIMESTAMP, + tenant_id BIGINT DEFAULT 0, + delete_flag CHAR(1) DEFAULT '0', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + create_by VARCHAR(64) +); + +CREATE INDEX idx_quality_score_encounter ON emr_quality_score(encounter_id); diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V74__infection_detail_stats.sql b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V74__infection_detail_stats.sql new file mode 100644 index 000000000..9fe546441 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V74__infection_detail_stats.sql @@ -0,0 +1,3 @@ +ALTER TABLE hir_infection_case ADD COLUMN IF NOT EXISTS department_id BIGINT; +COMMENT ON COLUMN hir_infection_case.department_id IS '科室ID'; +CREATE INDEX IF NOT EXISTS idx_hir_infection_case_dept ON hir_infection_case(department_id); diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/domain/EmrQualityScore.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/domain/EmrQualityScore.java new file mode 100644 index 000000000..8a2040d9a --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/domain/EmrQualityScore.java @@ -0,0 +1,47 @@ +package com.healthlink.his.emr.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +@Data +@TableName("emr_quality_score") +@Accessors(chain = true) +public class EmrQualityScore implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + private Long encounterId; + + private String emrType; + + private BigDecimal totalScore; + + private BigDecimal completenessScore; + + private BigDecimal timelinessScore; + + private BigDecimal accuracyScore; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date checkTime; + + private Integer tenantId; + + private String deleteFlag; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + private String createBy; +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/domain/EmrStructuredData.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/domain/EmrStructuredData.java new file mode 100644 index 000000000..4f0fef668 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/domain/EmrStructuredData.java @@ -0,0 +1,48 @@ +package com.healthlink.his.emr.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.Date; + +@Data +@TableName("emr_structured_data") +@Accessors(chain = true) +public class EmrStructuredData implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + private Long emrId; + + private Long encounterId; + + private Long patientId; + + private String dataType; + + private String dataKey; + + private String dataValue; + + private String dataUnit; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date recordTime; + + private Integer tenantId; + + private String deleteFlag; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + private String createBy; +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/mapper/EmrQualityScoreMapper.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/mapper/EmrQualityScoreMapper.java new file mode 100644 index 000000000..d33bfac38 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/mapper/EmrQualityScoreMapper.java @@ -0,0 +1,14 @@ +package com.healthlink.his.emr.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.healthlink.his.emr.domain.EmrQualityScore; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface EmrQualityScoreMapper extends BaseMapper { + + List selectByEncounterId(@Param("encounterId") Long encounterId); +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/mapper/EmrStructuredDataMapper.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/mapper/EmrStructuredDataMapper.java new file mode 100644 index 000000000..8c001b8b7 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/mapper/EmrStructuredDataMapper.java @@ -0,0 +1,16 @@ +package com.healthlink.his.emr.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.healthlink.his.emr.domain.EmrStructuredData; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface EmrStructuredDataMapper extends BaseMapper { + + List selectByEncounterId(@Param("encounterId") Long encounterId); + + List selectByEmrId(@Param("emrId") Long emrId); +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/IEmrQualityScoreService.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/IEmrQualityScoreService.java new file mode 100644 index 000000000..3913a53e5 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/IEmrQualityScoreService.java @@ -0,0 +1,11 @@ +package com.healthlink.his.emr.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.healthlink.his.emr.domain.EmrQualityScore; + +import java.util.List; + +public interface IEmrQualityScoreService extends IService { + + List selectByEncounterId(Long encounterId); +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/IEmrStructuredDataService.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/IEmrStructuredDataService.java new file mode 100644 index 000000000..0e0918b01 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/IEmrStructuredDataService.java @@ -0,0 +1,13 @@ +package com.healthlink.his.emr.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.healthlink.his.emr.domain.EmrStructuredData; + +import java.util.List; + +public interface IEmrStructuredDataService extends IService { + + List selectByEncounterId(Long encounterId); + + List selectByEmrId(Long emrId); +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/impl/EmrQualityScoreServiceImpl.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/impl/EmrQualityScoreServiceImpl.java new file mode 100644 index 000000000..a9b853720 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/impl/EmrQualityScoreServiceImpl.java @@ -0,0 +1,20 @@ +package com.healthlink.his.emr.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.healthlink.his.emr.domain.EmrQualityScore; +import com.healthlink.his.emr.mapper.EmrQualityScoreMapper; +import com.healthlink.his.emr.service.IEmrQualityScoreService; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class EmrQualityScoreServiceImpl + extends ServiceImpl + implements IEmrQualityScoreService { + + @Override + public List selectByEncounterId(Long encounterId) { + return baseMapper.selectByEncounterId(encounterId); + } +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/impl/EmrStructuredDataServiceImpl.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/impl/EmrStructuredDataServiceImpl.java new file mode 100644 index 000000000..51594eb74 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/emr/service/impl/EmrStructuredDataServiceImpl.java @@ -0,0 +1,25 @@ +package com.healthlink.his.emr.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.healthlink.his.emr.domain.EmrStructuredData; +import com.healthlink.his.emr.mapper.EmrStructuredDataMapper; +import com.healthlink.his.emr.service.IEmrStructuredDataService; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class EmrStructuredDataServiceImpl + extends ServiceImpl + implements IEmrStructuredDataService { + + @Override + public List selectByEncounterId(Long encounterId) { + return baseMapper.selectByEncounterId(encounterId); + } + + @Override + public List selectByEmrId(Long emrId) { + return baseMapper.selectByEmrId(emrId); + } +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/domain/RegionalShareRecord.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/domain/RegionalShareRecord.java new file mode 100644 index 000000000..48b94fadb --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/domain/RegionalShareRecord.java @@ -0,0 +1,26 @@ +package com.healthlink.his.esb.domain; + +import com.baomidou.mybatisplus.annotation.*; +import com.core.common.core.domain.HisBaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 区域医疗信息共享记录 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("regional_share_record") +public class RegionalShareRecord extends HisBaseEntity { + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + private Long encounterId; + private Long patientId; + private String shareType; + private String targetSystem; + private String shareStatus; + private String requestData; + private String responseData; + private String errorMessage; + private Integer retryCount; +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/mapper/RegionalShareRecordMapper.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/mapper/RegionalShareRecordMapper.java new file mode 100644 index 000000000..a45c7a706 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/mapper/RegionalShareRecordMapper.java @@ -0,0 +1,9 @@ +package com.healthlink.his.esb.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.healthlink.his.esb.domain.RegionalShareRecord; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface RegionalShareRecordMapper extends BaseMapper { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/service/IRegionalShareRecordService.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/service/IRegionalShareRecordService.java new file mode 100644 index 000000000..184211599 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/service/IRegionalShareRecordService.java @@ -0,0 +1,7 @@ +package com.healthlink.his.esb.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.healthlink.his.esb.domain.RegionalShareRecord; + +public interface IRegionalShareRecordService extends IService { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/service/impl/RegionalShareRecordServiceImpl.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/service/impl/RegionalShareRecordServiceImpl.java new file mode 100644 index 000000000..aae155c3f --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/esb/service/impl/RegionalShareRecordServiceImpl.java @@ -0,0 +1,11 @@ +package com.healthlink.his.esb.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.healthlink.his.esb.domain.RegionalShareRecord; +import com.healthlink.his.esb.mapper.RegionalShareRecordMapper; +import com.healthlink.his.esb.service.IRegionalShareRecordService; +import org.springframework.stereotype.Service; + +@Service +public class RegionalShareRecordServiceImpl extends ServiceImpl implements IRegionalShareRecordService { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/domain/CdssAlert.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/domain/CdssAlert.java new file mode 100644 index 000000000..58c23f128 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/domain/CdssAlert.java @@ -0,0 +1,35 @@ +package com.healthlink.his.infection.domain; + +import com.baomidou.mybatisplus.annotation.*; +import com.core.common.core.domain.HisBaseEntity; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("cdss_alert") +public class CdssAlert extends HisBaseEntity { + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + @TableField("encounter_id") + private Long encounterId; + @TableField("patient_id") + private Long patientId; + @TableField("rule_id") + private Long ruleId; + @TableField("alert_type") + private String alertType; + @TableField("alert_message") + private String alertMessage; + @TableField("severity") + private String severity; + @TableField("acknowledged") + private Boolean acknowledged; + @TableField("acknowledged_by") + private Long acknowledgedBy; + @TableField("acknowledged_time") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date acknowledgedTime; +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/domain/CdssRule.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/domain/CdssRule.java new file mode 100644 index 000000000..0774ab288 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/domain/CdssRule.java @@ -0,0 +1,30 @@ +package com.healthlink.his.infection.domain; + +import com.baomidou.mybatisplus.annotation.*; +import com.core.common.core.domain.HisBaseEntity; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("cdss_rule") +public class CdssRule extends HisBaseEntity { + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + @TableField("rule_code") + private String ruleCode; + @TableField("rule_name") + private String ruleName; + @TableField("rule_type") + private String ruleType; + @TableField("condition_expr") + private String conditionExpr; + @TableField("suggestion") + private String suggestion; + @TableField("severity") + private String severity; + @TableField("enabled") + private Boolean enabled; +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/mapper/CdssAlertMapper.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/mapper/CdssAlertMapper.java new file mode 100644 index 000000000..46e956005 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/mapper/CdssAlertMapper.java @@ -0,0 +1,9 @@ +package com.healthlink.his.infection.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.healthlink.his.infection.domain.CdssAlert; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CdssAlertMapper extends BaseMapper { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/mapper/CdssRuleMapper.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/mapper/CdssRuleMapper.java new file mode 100644 index 000000000..44e5f66e3 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/mapper/CdssRuleMapper.java @@ -0,0 +1,9 @@ +package com.healthlink.his.infection.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.healthlink.his.infection.domain.CdssRule; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CdssRuleMapper extends BaseMapper { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/ICdssAlertService.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/ICdssAlertService.java new file mode 100644 index 000000000..2bdfdee86 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/ICdssAlertService.java @@ -0,0 +1,7 @@ +package com.healthlink.his.infection.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.healthlink.his.infection.domain.CdssAlert; + +public interface ICdssAlertService extends IService { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/ICdssRuleService.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/ICdssRuleService.java new file mode 100644 index 000000000..4c8b8f104 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/ICdssRuleService.java @@ -0,0 +1,7 @@ +package com.healthlink.his.infection.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.healthlink.his.infection.domain.CdssRule; + +public interface ICdssRuleService extends IService { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/impl/CdssAlertServiceImpl.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/impl/CdssAlertServiceImpl.java new file mode 100644 index 000000000..75c38e3ff --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/impl/CdssAlertServiceImpl.java @@ -0,0 +1,11 @@ +package com.healthlink.his.infection.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.healthlink.his.infection.domain.CdssAlert; +import com.healthlink.his.infection.mapper.CdssAlertMapper; +import com.healthlink.his.infection.service.ICdssAlertService; +import org.springframework.stereotype.Service; + +@Service +public class CdssAlertServiceImpl extends ServiceImpl implements ICdssAlertService { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/impl/CdssRuleServiceImpl.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/impl/CdssRuleServiceImpl.java new file mode 100644 index 000000000..ca45560aa --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/infection/service/impl/CdssRuleServiceImpl.java @@ -0,0 +1,11 @@ +package com.healthlink.his.infection.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.healthlink.his.infection.domain.CdssRule; +import com.healthlink.his.infection.mapper.CdssRuleMapper; +import com.healthlink.his.infection.service.ICdssRuleService; +import org.springframework.stereotype.Service; + +@Service +public class CdssRuleServiceImpl extends ServiceImpl implements ICdssRuleService { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/resources/mapper/emr/EmrQualityScoreMapper.xml b/healthlink-his-server/healthlink-his-domain/src/main/resources/mapper/emr/EmrQualityScoreMapper.xml new file mode 100644 index 000000000..d05656118 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/resources/mapper/emr/EmrQualityScoreMapper.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/healthlink-his-server/healthlink-his-domain/src/main/resources/mapper/emr/EmrStructuredDataMapper.xml b/healthlink-his-server/healthlink-his-domain/src/main/resources/mapper/emr/EmrStructuredDataMapper.xml new file mode 100644 index 000000000..181821e9c --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/resources/mapper/emr/EmrStructuredDataMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/healthlink-his-ui/src/api/emr/index.js b/healthlink-his-ui/src/api/emr/index.js index 3a7487460..5494732b0 100644 --- a/healthlink-his-ui/src/api/emr/index.js +++ b/healthlink-his-ui/src/api/emr/index.js @@ -14,3 +14,8 @@ export function getEmrRevisionList(emrId) { return request({ url: '/emr/revision export function getEmrRevisionPage(params) { return request({ url: '/emr/revision/page', method: 'get', params }) } export function getEmrRevisionDetail(id) { return request({ url: '/emr/revision/' + id, method: 'get' }) } export function compareEmrRevisions(id1, id2) { return request({ url: '/emr/revision/compare', method: 'get', params: { revisionId1: id1, revisionId2: id2 } }) } + +export function extractStructuredData(emrId) { return request({ url: '/emr/warehouse/extract', method: 'post', params: { emrId } }) } +export function getStructuredData(encounterId) { return request({ url: '/emr/warehouse/data/' + encounterId, method: 'get' }) } +export function calculateQualityScore(encounterId) { return request({ url: '/emr/warehouse/quality-score', method: 'post', params: { encounterId } }) } +export function getQualityScores(encounterId) { return request({ url: '/emr/warehouse/quality-scores', method: 'get', params: { encounterId } }) } From 0b0e25e1a0f95337cf8a2257dc5696f1269ef982 Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 17:23:32 +0800 Subject: [PATCH 05/10] =?UTF-8?q?feat(emr):=20=E5=AE=9E=E7=8E=B0=E7=94=B5?= =?UTF-8?q?=E5=AD=90=E7=97=85=E5=8E=86=E7=BB=93=E6=9E=84=E5=8C=96=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E4=BB=93=E5=BA=93=E5=92=8C=E8=B4=A8=E9=87=8F=E8=AF=84?= =?UTF-8?q?=E5=88=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加EMR结构化数据提取、存储和查询功能 - 实现病历质量评分计算,包括完整性、及时性和准确性指标 - 新增CDSS临床决策支持服务和告警管理功能 - 实现区域医疗信息共享数据交换功能 - 添加院感监测统计分析功能 - 更新数据库迁移脚本,创建相关数据表结构 - 修正菜单图标大小写问题 --- MD/~$ALTHLINK_HIS_PRICING_PROPOSAL_0.2.docx | Bin 162 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 MD/~$ALTHLINK_HIS_PRICING_PROPOSAL_0.2.docx diff --git a/MD/~$ALTHLINK_HIS_PRICING_PROPOSAL_0.2.docx b/MD/~$ALTHLINK_HIS_PRICING_PROPOSAL_0.2.docx deleted file mode 100644 index fcd42e3fa9819ae3c2e99e74ae57ff52b9d59275..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 xcmd;eOv%m6%PcM_N-W7QVjvRmGB`4%Fyu01GUNeqF+&MM5kn%7%m>oc1^`8%3f%wz From 0752f53966f82bfd7a83a92fb12fd03b37f5ab65 Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 17:24:56 +0800 Subject: [PATCH 06/10] =?UTF-8?q?feat(infection):=20=E9=99=A2=E6=84=9F?= =?UTF-8?q?=E7=9B=91=E6=B5=8B=E7=BB=86=E5=8C=96=20=E2=80=94=20=E7=A7=91?= =?UTF-8?q?=E5=AE=A4=E6=84=9F=E6=9F=93=E7=8E=87+=E6=84=9F=E6=9F=93?= =?UTF-8?q?=E8=B6=8B=E5=8A=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 IInfectionDetailAppService + InfectionDetailAppServiceImpl - 新增 InfectionDetailController (GET /rate-by-dept, GET /trend) - 新增 V74 迁移脚本: hir_infection_case 加 department_id - 前端 InfectionDetailStats.vue 统计面板+趋势表格 --- healthlink-his-ui/src/api/infection/detail.js | 9 ++ .../src/views/infection/detail/index.vue | 152 ++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 healthlink-his-ui/src/api/infection/detail.js create mode 100644 healthlink-his-ui/src/views/infection/detail/index.vue diff --git a/healthlink-his-ui/src/api/infection/detail.js b/healthlink-his-ui/src/api/infection/detail.js new file mode 100644 index 000000000..8d512718e --- /dev/null +++ b/healthlink-his-ui/src/api/infection/detail.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +export function getInfectionRateByDept(params) { + return request({ url: '/infection-detail/rate-by-dept', method: 'get', params }) +} + +export function getInfectionTrend(params) { + return request({ url: '/infection-detail/trend', method: 'get', params }) +} diff --git a/healthlink-his-ui/src/views/infection/detail/index.vue b/healthlink-his-ui/src/views/infection/detail/index.vue new file mode 100644 index 000000000..042943eb6 --- /dev/null +++ b/healthlink-his-ui/src/views/infection/detail/index.vue @@ -0,0 +1,152 @@ + + + + + From cba192401e81a279462e26be1c29e8ffa4e0a874 Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 17:29:20 +0800 Subject: [PATCH 07/10] =?UTF-8?q?feat(cdss):=20CDSS=E4=B8=B4=E5=BA=8A?= =?UTF-8?q?=E5=86=B3=E7=AD=96=E6=94=AF=E6=8C=81=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/infection/cdss/CdssAlerts.vue | 115 ++++++++++++++++++ .../src/views/infection/cdss/CdssRules.vue | 97 +++++++++++++++ .../src/views/infection/cdss/api.js | 17 +++ 3 files changed, 229 insertions(+) create mode 100644 healthlink-his-ui/src/views/infection/cdss/CdssAlerts.vue create mode 100644 healthlink-his-ui/src/views/infection/cdss/CdssRules.vue create mode 100644 healthlink-his-ui/src/views/infection/cdss/api.js diff --git a/healthlink-his-ui/src/views/infection/cdss/CdssAlerts.vue b/healthlink-his-ui/src/views/infection/cdss/CdssAlerts.vue new file mode 100644 index 000000000..2a780ddaf --- /dev/null +++ b/healthlink-his-ui/src/views/infection/cdss/CdssAlerts.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/healthlink-his-ui/src/views/infection/cdss/CdssRules.vue b/healthlink-his-ui/src/views/infection/cdss/CdssRules.vue new file mode 100644 index 000000000..9946e0247 --- /dev/null +++ b/healthlink-his-ui/src/views/infection/cdss/CdssRules.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/healthlink-his-ui/src/views/infection/cdss/api.js b/healthlink-his-ui/src/views/infection/cdss/api.js new file mode 100644 index 000000000..919fec373 --- /dev/null +++ b/healthlink-his-ui/src/views/infection/cdss/api.js @@ -0,0 +1,17 @@ +import request from '@/utils/request' + +export function evaluateRules(encounterId) { + return request({ url: '/infection/cdss/evaluate', method: 'post', params: { encounterId } }) +} + +export function getAlerts(encounterId) { + return request({ url: '/infection/cdss/alerts/' + encounterId, method: 'get' }) +} + +export function acknowledgeAlert(alertId) { + return request({ url: '/infection/cdss/alerts/' + alertId + '/acknowledge', method: 'post' }) +} + +export function getRules(params) { + return request({ url: '/infection/cdss/rules', method: 'get', params }) +} From f0e189ca8e14131c680e050e3565c71a808881f0 Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 17:29:50 +0800 Subject: [PATCH 08/10] =?UTF-8?q?feat(regional):=20=E5=8C=BA=E5=9F=9F?= =?UTF-8?q?=E5=8C=BB=E7=96=97=E4=BF=A1=E6=81=AF=E5=85=B1=E4=BA=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/emr/data-warehouse/index.vue | 193 ++++++++++++++ .../src/views/esbmanage/regionalshare/api.js | 13 + .../views/esbmanage/regionalshare/index.vue | 240 ++++++++++++++++++ 3 files changed, 446 insertions(+) create mode 100644 healthlink-his-ui/src/views/emr/data-warehouse/index.vue create mode 100644 healthlink-his-ui/src/views/esbmanage/regionalshare/api.js create mode 100644 healthlink-his-ui/src/views/esbmanage/regionalshare/index.vue diff --git a/healthlink-his-ui/src/views/emr/data-warehouse/index.vue b/healthlink-his-ui/src/views/emr/data-warehouse/index.vue new file mode 100644 index 000000000..bf5d2e5b9 --- /dev/null +++ b/healthlink-his-ui/src/views/emr/data-warehouse/index.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/healthlink-his-ui/src/views/esbmanage/regionalshare/api.js b/healthlink-his-ui/src/views/esbmanage/regionalshare/api.js new file mode 100644 index 000000000..b34d0b323 --- /dev/null +++ b/healthlink-his-ui/src/views/esbmanage/regionalshare/api.js @@ -0,0 +1,13 @@ +import request from '@/utils/request' + +export function sharePatientData(params) { + return request({ url: '/regional/share', method: 'post', params }) +} + +export function getShareRecords(encounterId) { + return request({ url: '/regional/share/records/' + encounterId, method: 'get' }) +} + +export function getShareStats() { + return request({ url: '/regional/share/stats', method: 'get' }) +} diff --git a/healthlink-his-ui/src/views/esbmanage/regionalshare/index.vue b/healthlink-his-ui/src/views/esbmanage/regionalshare/index.vue new file mode 100644 index 000000000..4a62a5a0b --- /dev/null +++ b/healthlink-his-ui/src/views/esbmanage/regionalshare/index.vue @@ -0,0 +1,240 @@ + + + + + From ea0821ee3d8e44ef89b6f1227cb5f6f51ed9b528 Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 17:30:17 +0800 Subject: [PATCH 09/10] =?UTF-8?q?feat(mrhomepage):=20=E7=97=85=E6=A1=88?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E7=BB=86=E5=8C=96=20=E2=80=94=20=E7=A7=91?= =?UTF-8?q?=E5=AE=A4=E7=BB=9F=E8=AE=A1+=E5=8C=BB=E7=94=9F=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 IMrStatsDetailAppService + MrStatsDetailAppServiceImpl - 新增 MrStatsDetailController (GET /by-dept, GET /by-doctor) - 新增 V75 迁移脚本: mr_homepage 加 department_id, doctor_id - 前端 MrStatsDetail.vue 统计面板+DRG/诊断分布 --- .../appservice/IMrStatsDetailAppService.java | 11 ++ .../impl/MrStatsDetailAppServiceImpl.java | 95 +++++++++++ .../controller/MrStatsDetailController.java | 38 +++++ .../db/migration/V75__mr_stats_detail.sql | 6 + .../src/api/mrhomepage/statsDetail.js | 9 + .../mrhomepage/statistics-detail/index.vue | 156 ++++++++++++++++++ 6 files changed, 315 insertions(+) create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/appservice/IMrStatsDetailAppService.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/appservice/impl/MrStatsDetailAppServiceImpl.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/controller/MrStatsDetailController.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V75__mr_stats_detail.sql create mode 100644 healthlink-his-ui/src/api/mrhomepage/statsDetail.js create mode 100644 healthlink-his-ui/src/views/mrhomepage/statistics-detail/index.vue diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/appservice/IMrStatsDetailAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/appservice/IMrStatsDetailAppService.java new file mode 100644 index 000000000..aeb3f4aad --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/appservice/IMrStatsDetailAppService.java @@ -0,0 +1,11 @@ +package com.healthlink.his.web.mrhomepage.appservice; + +import java.util.List; +import java.util.Map; + +public interface IMrStatsDetailAppService { + + Map getMrStatsByDept(Long deptId); + + Map getMrStatsByDoctor(Long doctorId); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/appservice/impl/MrStatsDetailAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/appservice/impl/MrStatsDetailAppServiceImpl.java new file mode 100644 index 000000000..fc44d8e82 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/appservice/impl/MrStatsDetailAppServiceImpl.java @@ -0,0 +1,95 @@ +package com.healthlink.his.web.mrhomepage.appservice.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.healthlink.his.mrhomepage.domain.MrHomepage; +import com.healthlink.his.mrhomepage.service.IMrHomepageService; +import com.healthlink.his.web.mrhomepage.appservice.IMrStatsDetailAppService; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@AllArgsConstructor +public class MrStatsDetailAppServiceImpl implements IMrStatsDetailAppService { + + private final IMrHomepageService mrHomepageService; + + @Override + public Map getMrStatsByDept(Long deptId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (deptId != null) { + wrapper.eq(MrHomepage::getEncounterId, deptId); + } + List list = mrHomepageService.list(wrapper); + + Map result = new HashMap<>(); + result.put("totalCount", list.size()); + + BigDecimal totalCost = list.stream() + .map(MrHomepage::getTotalCost) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + result.put("totalCost", totalCost); + + result.put("avgCost", list.isEmpty() ? BigDecimal.ZERO : + totalCost.divide(BigDecimal.valueOf(list.size()), 2, RoundingMode.HALF_UP)); + + Map byStatus = list.stream() + .collect(Collectors.groupingBy( + h -> h.getQualityStatus() != null ? h.getQualityStatus() : "UNKNOWN", + Collectors.counting())); + result.put("byStatus", byStatus); + + Map byDrg = list.stream() + .filter(h -> h.getDrgGroup() != null) + .collect(Collectors.groupingBy(MrHomepage::getDrgGroup, Collectors.counting())); + result.put("byDrg", byDrg); + + long totalLos = list.stream() + .mapToInt(h -> h.getLosDays() != null ? h.getLosDays() : 0) + .sum(); + result.put("totalLosDays", totalLos); + result.put("avgLosDays", list.isEmpty() ? 0 : + Math.round(totalLos * 10.0 / list.size()) / 10.0); + + return result; + } + + @Override + public Map getMrStatsByDoctor(Long doctorId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (doctorId != null) { + wrapper.eq(MrHomepage::getPatientId, doctorId); + } + List list = mrHomepageService.list(wrapper); + + Map result = new HashMap<>(); + result.put("totalCount", list.size()); + + BigDecimal totalCost = list.stream() + .map(MrHomepage::getTotalCost) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + result.put("totalCost", totalCost); + + result.put("avgCost", list.isEmpty() ? BigDecimal.ZERO : + totalCost.divide(BigDecimal.valueOf(list.size()), 2, RoundingMode.HALF_UP)); + + Map byStatus = list.stream() + .collect(Collectors.groupingBy( + h -> h.getQualityStatus() != null ? h.getQualityStatus() : "UNKNOWN", + Collectors.counting())); + result.put("byStatus", byStatus); + + Map byDiagnosis = list.stream() + .filter(h -> h.getPrimaryDiagnosisName() != null) + .collect(Collectors.groupingBy(MrHomepage::getPrimaryDiagnosisName, Collectors.counting())); + result.put("byDiagnosis", byDiagnosis); + + return result; + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/controller/MrStatsDetailController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/controller/MrStatsDetailController.java new file mode 100644 index 000000000..b3d3ba2ed --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/controller/MrStatsDetailController.java @@ -0,0 +1,38 @@ +package com.healthlink.his.web.mrhomepage.controller; + +import com.core.common.core.domain.R; +import com.healthlink.his.web.mrhomepage.appservice.IMrStatsDetailAppService; +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.web.bind.annotation.*; + +import java.util.Map; + +@Tag(name = "病案统计明细") +@RestController +@RequestMapping("/mr-stats-detail") +@Slf4j +@AllArgsConstructor +public class MrStatsDetailController { + + private final IMrStatsDetailAppService mrStatsDetailAppService; + + @Operation(summary = "科室病案统计") + @PreAuthorize("@ss.hasPermi('mrhomepage:mrhomepage:list')") + @GetMapping("/by-dept") + public R> getMrStatsByDept( + @RequestParam(value = "deptId", required = false) Long deptId) { + return R.ok(mrStatsDetailAppService.getMrStatsByDept(deptId)); + } + + @Operation(summary = "医生病案统计") + @PreAuthorize("@ss.hasPermi('mrhomepage:mrhomepage:list')") + @GetMapping("/by-doctor") + public R> getMrStatsByDoctor( + @RequestParam(value = "doctorId", required = false) Long doctorId) { + return R.ok(mrStatsDetailAppService.getMrStatsByDoctor(doctorId)); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V75__mr_stats_detail.sql b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V75__mr_stats_detail.sql new file mode 100644 index 000000000..059e1e142 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V75__mr_stats_detail.sql @@ -0,0 +1,6 @@ +ALTER TABLE mr_homepage ADD COLUMN IF NOT EXISTS department_id BIGINT; +ALTER TABLE mr_homepage ADD COLUMN IF NOT EXISTS doctor_id BIGINT; +COMMENT ON COLUMN mr_homepage.department_id IS '科室ID'; +COMMENT ON COLUMN mr_homepage.doctor_id IS '主治医生ID'; +CREATE INDEX IF NOT EXISTS idx_mr_homepage_dept ON mr_homepage(department_id); +CREATE INDEX IF NOT EXISTS idx_mr_homepage_doctor ON mr_homepage(doctor_id); diff --git a/healthlink-his-ui/src/api/mrhomepage/statsDetail.js b/healthlink-his-ui/src/api/mrhomepage/statsDetail.js new file mode 100644 index 000000000..c3852dd31 --- /dev/null +++ b/healthlink-his-ui/src/api/mrhomepage/statsDetail.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +export function getMrStatsByDept(params) { + return request({ url: '/mr-stats-detail/by-dept', method: 'get', params }) +} + +export function getMrStatsByDoctor(params) { + return request({ url: '/mr-stats-detail/by-doctor', method: 'get', params }) +} diff --git a/healthlink-his-ui/src/views/mrhomepage/statistics-detail/index.vue b/healthlink-his-ui/src/views/mrhomepage/statistics-detail/index.vue new file mode 100644 index 000000000..fd7d2926b --- /dev/null +++ b/healthlink-his-ui/src/views/mrhomepage/statistics-detail/index.vue @@ -0,0 +1,156 @@ + + + + + From 0994550f2f4b99ea8babf9e71a517a9c736ecc8b Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 17:34:34 +0800 Subject: [PATCH 10/10] =?UTF-8?q?Revert=20"feat(cdss):=20CDSS=E4=B8=B4?= =?UTF-8?q?=E5=BA=8A=E5=86=B3=E7=AD=96=E6=94=AF=E6=8C=81=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit cba192401e81a279462e26be1c29e8ffa4e0a874. --- .../src/views/infection/cdss/CdssAlerts.vue | 115 ------------------ .../src/views/infection/cdss/CdssRules.vue | 97 --------------- .../src/views/infection/cdss/api.js | 17 --- 3 files changed, 229 deletions(-) delete mode 100644 healthlink-his-ui/src/views/infection/cdss/CdssAlerts.vue delete mode 100644 healthlink-his-ui/src/views/infection/cdss/CdssRules.vue delete mode 100644 healthlink-his-ui/src/views/infection/cdss/api.js diff --git a/healthlink-his-ui/src/views/infection/cdss/CdssAlerts.vue b/healthlink-his-ui/src/views/infection/cdss/CdssAlerts.vue deleted file mode 100644 index 2a780ddaf..000000000 --- a/healthlink-his-ui/src/views/infection/cdss/CdssAlerts.vue +++ /dev/null @@ -1,115 +0,0 @@ - - - - - diff --git a/healthlink-his-ui/src/views/infection/cdss/CdssRules.vue b/healthlink-his-ui/src/views/infection/cdss/CdssRules.vue deleted file mode 100644 index 9946e0247..000000000 --- a/healthlink-his-ui/src/views/infection/cdss/CdssRules.vue +++ /dev/null @@ -1,97 +0,0 @@ - - - - - diff --git a/healthlink-his-ui/src/views/infection/cdss/api.js b/healthlink-his-ui/src/views/infection/cdss/api.js deleted file mode 100644 index 919fec373..000000000 --- a/healthlink-his-ui/src/views/infection/cdss/api.js +++ /dev/null @@ -1,17 +0,0 @@ -import request from '@/utils/request' - -export function evaluateRules(encounterId) { - return request({ url: '/infection/cdss/evaluate', method: 'post', params: { encounterId } }) -} - -export function getAlerts(encounterId) { - return request({ url: '/infection/cdss/alerts/' + encounterId, method: 'get' }) -} - -export function acknowledgeAlert(alertId) { - return request({ url: '/infection/cdss/alerts/' + alertId + '/acknowledge', method: 'post' }) -} - -export function getRules(params) { - return request({ url: '/infection/cdss/rules', method: 'get', params }) -}