feat(mrhomepage): HQMS首页上报

This commit is contained in:
2026-06-17 14:14:45 +08:00
parent 00604b2d01
commit 1df1f0d1ad
11 changed files with 477 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
package com.healthlink.his.web.mrhomepage.appservice;
import com.healthlink.his.mrhomepage.domain.MrHqmsReport;
import java.util.List;
import java.util.Map;
public interface IMrHqmsReportAppService {
MrHqmsReport submitReport(Long homepageId, String reportType);
Map<String, Object> getReportStatus(Long homepageId);
List<MrHqmsReport> listReports(Long homepageId);
}

View File

@@ -0,0 +1,105 @@
package com.healthlink.his.web.mrhomepage.appservice.impl;
import com.healthlink.his.mrhomepage.domain.MrHomepage;
import com.healthlink.his.mrhomepage.domain.MrHqmsReport;
import com.healthlink.his.mrhomepage.service.IMrHomepageService;
import com.healthlink.his.mrhomepage.service.IMrHqmsReportService;
import com.healthlink.his.web.mrhomepage.appservice.IMrHqmsReportAppService;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class MrHqmsReportAppServiceImpl implements IMrHqmsReportAppService {
private static final Logger log = LoggerFactory.getLogger(MrHqmsReportAppServiceImpl.class);
@Resource
private IMrHomepageService mrHomepageService;
@Resource
private IMrHqmsReportService mrHqmsReportService;
@Override
@Transactional
public MrHqmsReport submitReport(Long homepageId, String reportType) {
MrHomepage homepage = mrHomepageService.getById(homepageId);
if (homepage == null) {
throw new RuntimeException("病案首页不存在: " + homepageId);
}
MrHqmsReport report = new MrHqmsReport()
.setHomepageId(homepageId)
.setEncounterId(homepage.getEncounterId())
.setPatientId(homepage.getPatientId())
.setReportType(reportType != null ? reportType : "HQMS")
.setReportStatus("SUBMITTED")
.setReportTime(new Date())
.setRetryCount(0);
String reportData = buildReportData(homepage);
report.setReportData(reportData);
mrHqmsReportService.save(report);
log.info("HQMS上报已提交: homepageId={}, reportId={}", homepageId, report.getId());
return report;
}
@Override
public Map<String, Object> getReportStatus(Long homepageId) {
List<MrHqmsReport> reports = mrHqmsReportService.selectByHomepageId(homepageId);
Map<String, Object> status = new HashMap<>();
status.put("homepageId", homepageId);
status.put("totalReports", reports.size());
long successCount = reports.stream()
.filter(r -> "SUCCESS".equals(r.getReportStatus()))
.count();
long pendingCount = reports.stream()
.filter(r -> "PENDING".equals(r.getReportStatus()) || "SUBMITTED".equals(r.getReportStatus()))
.count();
long failedCount = reports.stream()
.filter(r -> "FAILED".equals(r.getReportStatus()))
.count();
status.put("successCount", successCount);
status.put("pendingCount", pendingCount);
status.put("failedCount", failedCount);
status.put("reports", reports);
return status;
}
@Override
public List<MrHqmsReport> listReports(Long homepageId) {
return mrHqmsReportService.selectByHomepageId(homepageId);
}
private String buildReportData(MrHomepage homepage) {
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append("\"homepageId\":").append(homepage.getId());
sb.append(",\"encounterId\":").append(homepage.getEncounterId());
sb.append(",\"patientId\":").append(homepage.getPatientId());
sb.append(",\"admissionDate\":\"").append(homepage.getAdmissionDate() != null ? homepage.getAdmissionDate() : "").append("\"");
sb.append(",\"dischargeDate\":\"").append(homepage.getDischargeDate() != null ? homepage.getDischargeDate() : "").append("\"");
sb.append(",\"primaryDiagnosisCode\":\"").append(homepage.getPrimaryDiagnosisCode() != null ? homepage.getPrimaryDiagnosisCode() : "").append("\"");
sb.append(",\"primaryDiagnosisName\":\"").append(homepage.getPrimaryDiagnosisName() != null ? homepage.getPrimaryDiagnosisName() : "").append("\"");
sb.append(",\"primaryProcedureCode\":\"").append(homepage.getPrimaryProcedureCode() != null ? homepage.getPrimaryProcedureCode() : "").append("\"");
sb.append(",\"primaryProcedureName\":\"").append(homepage.getPrimaryProcedureName() != null ? homepage.getPrimaryProcedureName() : "").append("\"");
sb.append(",\"drgGroup\":\"").append(homepage.getDrgGroup() != null ? homepage.getDrgGroup() : "").append("\"");
sb.append(",\"totalCost\":").append(homepage.getTotalCost() != null ? homepage.getTotalCost() : 0);
sb.append(",\"losDays\":").append(homepage.getLosDays() != null ? homepage.getLosDays() : 0);
sb.append("}");
return sb.toString();
}
}

View File

@@ -0,0 +1,45 @@
package com.healthlink.his.web.mrhomepage.controller;
import com.core.common.core.domain.R;
import com.healthlink.his.mrhomepage.domain.MrHqmsReport;
import com.healthlink.his.web.mrhomepage.appservice.IMrHqmsReportAppService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/mr-homepage/hqms")
@Tag(name = "HQMS首页上报")
public class MrHqmsReportController {
@Resource
private IMrHqmsReportAppService mrHqmsReportAppService;
@PostMapping("/report")
@PreAuthorize("hasAuthority('inpatient:mrhomepage:edit')")
@Operation(summary = "提交HQMS上报")
public R<MrHqmsReport> submitReport(
@RequestParam Long homepageId,
@RequestParam(required = false, defaultValue = "HQMS") String reportType) {
return R.ok(mrHqmsReportAppService.submitReport(homepageId, reportType));
}
@GetMapping("/status/{homepageId}")
@PreAuthorize("hasAuthority('inpatient:mrhomepage:list')")
@Operation(summary = "查询上报状态")
public R<Map<String, Object>> getReportStatus(@PathVariable Long homepageId) {
return R.ok(mrHqmsReportAppService.getReportStatus(homepageId));
}
@GetMapping("/list/{homepageId}")
@PreAuthorize("hasAuthority('inpatient:mrhomepage:list')")
@Operation(summary = "查询上报记录列表")
public R<List<MrHqmsReport>> listReports(@PathVariable Long homepageId) {
return R.ok(mrHqmsReportAppService.listReports(homepageId));
}
}

View File

@@ -0,0 +1,27 @@
-- ============================================================
-- V65: HQMS首页上报表
-- ============================================================
CREATE TABLE mr_hqms_report (
id BIGSERIAL PRIMARY KEY,
homepage_id BIGINT NOT NULL,
encounter_id BIGINT NOT NULL,
patient_id BIGINT NOT NULL,
report_type VARCHAR(20) NOT NULL,
report_status VARCHAR(20) DEFAULT 'PENDING',
report_time TIMESTAMP,
report_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)
);
COMMENT ON TABLE mr_hqms_report IS 'HQMS首页上报记录';
CREATE INDEX idx_mr_hqms_report_homepage ON mr_hqms_report(homepage_id);
CREATE INDEX idx_mr_hqms_report_encounter ON mr_hqms_report(encounter_id);
CREATE INDEX idx_mr_hqms_report_status ON mr_hqms_report(report_status);

View File

@@ -0,0 +1,43 @@
package com.healthlink.his.mrhomepage.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.Date;
@Data
@TableName("mr_hqms_report")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class MrHqmsReport extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private Long homepageId;
private Long encounterId;
private Long patientId;
private String reportType;
private String reportStatus;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date reportTime;
private String reportData;
private String responseData;
private String errorMessage;
private Integer retryCount;
}

View File

@@ -0,0 +1,16 @@
package com.healthlink.his.mrhomepage.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.mrhomepage.domain.MrHqmsReport;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface MrHqmsReportMapper extends BaseMapper<MrHqmsReport> {
List<MrHqmsReport> selectByHomepageId(@Param("homepageId") Long homepageId);
List<MrHqmsReport> selectByEncounterId(@Param("encounterId") Long encounterId);
}

View File

@@ -0,0 +1,13 @@
package com.healthlink.his.mrhomepage.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.mrhomepage.domain.MrHqmsReport;
import java.util.List;
public interface IMrHqmsReportService extends IService<MrHqmsReport> {
List<MrHqmsReport> selectByHomepageId(Long homepageId);
List<MrHqmsReport> selectByEncounterId(Long encounterId);
}

View File

@@ -0,0 +1,25 @@
package com.healthlink.his.mrhomepage.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.mrhomepage.domain.MrHqmsReport;
import com.healthlink.his.mrhomepage.mapper.MrHqmsReportMapper;
import com.healthlink.his.mrhomepage.service.IMrHqmsReportService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MrHqmsReportServiceImpl
extends ServiceImpl<MrHqmsReportMapper, MrHqmsReport>
implements IMrHqmsReportService {
@Override
public List<MrHqmsReport> selectByHomepageId(Long homepageId) {
return baseMapper.selectByHomepageId(homepageId);
}
@Override
public List<MrHqmsReport> selectByEncounterId(Long encounterId) {
return baseMapper.selectByEncounterId(encounterId);
}
}

View File

@@ -3,3 +3,6 @@ export function executeQualityCheck(id) { return request({ url: "/api/v1/mr-home
export function submitHomepage(id) { return request({ url: "/api/v1/mr-homepage/submit/" + id, method: "put" }) }
export function checkQuality(homepageId) { return request({ url: "/api/v1/mr-homepage/quality/check", method: "post", params: { homepageId } }) }
export function getQualityResults(homepageId) { return request({ url: "/api/v1/mr-homepage/quality/results/" + homepageId, method: "get" }) }
export function submitHqmsReport(homepageId, reportType) { return request({ url: "/api/v1/mr-homepage/hqms/report", method: "post", params: { homepageId, reportType } }) }
export function getHqmsReportStatus(homepageId) { return request({ url: "/api/v1/mr-homepage/hqms/status/" + homepageId, method: "get" }) }
export function listHqmsReports(homepageId) { return request({ url: "/api/v1/mr-homepage/hqms/list/" + homepageId, method: "get" }) }

View File

@@ -8,3 +8,6 @@ export function getStatistics(params) { return request({ url: '/api/v1/mr-homepa
export function submitHomepage(id) { return request({ url: '/api/v1/mr-homepage/submit/' + id, method: 'put' }) }
export function checkQuality(homepageId) { return request({ url: '/api/v1/mr-homepage/quality/check', method: 'post', params: { homepageId } }) }
export function getQualityResults(homepageId) { return request({ url: '/api/v1/mr-homepage/quality/results/' + homepageId, method: 'get' }) }
export function submitHqmsReport(homepageId, reportType) { return request({ url: '/api/v1/mr-homepage/hqms/report', method: 'post', params: { homepageId, reportType } }) }
export function getHqmsReportStatus(homepageId) { return request({ url: '/api/v1/mr-homepage/hqms/status/' + homepageId, method: 'get' }) }
export function listHqmsReports(homepageId) { return request({ url: '/api/v1/mr-homepage/hqms/list/' + homepageId, method: 'get' }) }

View File

@@ -0,0 +1,182 @@
<template>
<div class="app-container">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>HQMS首页数据上报</span>
<el-button type="primary" @click="handleQuery">刷新</el-button>
</div>
</template>
<el-form :model="queryParams" :inline="true" style="margin-bottom: 16px">
<el-form-item label="首页ID">
<el-input v-model="queryParams.homepageId" placeholder="请输入首页ID" clearable style="width: 180px" />
</el-form-item>
<el-form-item label="上报状态">
<el-select v-model="queryParams.reportStatus" placeholder="全部" clearable style="width: 140px">
<el-option label="待上报" value="PENDING" />
<el-option label="已提交" value="SUBMITTED" />
<el-option label="成功" value="SUCCESS" />
<el-option label="失败" value="FAILED" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="dataList" border stripe>
<el-table-column label="上报ID" prop="id" width="100" />
<el-table-column label="首页ID" prop="homepageId" width="100" />
<el-table-column label="就诊ID" prop="encounterId" width="100" />
<el-table-column label="上报类型" prop="reportType" width="100">
<template #default="scope">
<el-tag size="small">{{ scope.row.reportType }}</el-tag>
</template>
</el-table-column>
<el-table-column label="上报状态" prop="reportStatus" width="100">
<template #default="scope">
<el-tag :type="statusMap[scope.row.reportStatus]?.type" size="small">
{{ statusMap[scope.row.reportStatus]?.label }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="上报时间" prop="reportTime" width="170" />
<el-table-column label="重试次数" prop="retryCount" width="80" align="center" />
<el-table-column label="错误信息" prop="errorMessage" show-overflow-tooltip />
<el-table-column label="操作" width="180" align="center">
<template #default="scope">
<el-button link type="primary" @click="handleDetail(scope.row)">详情</el-button>
<el-button
v-if="scope.row.reportStatus === 'FAILED'"
link
type="warning"
@click="handleRetry(scope.row)"
>
重试
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-show="total > 0"
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
style="margin-top: 12px; justify-content: flex-end"
:total="total"
layout="total, prev, pager, next"
@current-change="getList"
/>
</el-card>
<el-dialog v-model="detailVisible" title="上报详情" width="700px">
<el-descriptions :column="2" border>
<el-descriptions-item label="上报ID">{{ detailData.id }}</el-descriptions-item>
<el-descriptions-item label="首页ID">{{ detailData.homepageId }}</el-descriptions-item>
<el-descriptions-item label="就诊ID">{{ detailData.encounterId }}</el-descriptions-item>
<el-descriptions-item label="患者ID">{{ detailData.patientId }}</el-descriptions-item>
<el-descriptions-item label="上报类型">{{ detailData.reportType }}</el-descriptions-item>
<el-descriptions-item label="上报状态">
<el-tag :type="statusMap[detailData.reportStatus]?.type">
{{ statusMap[detailData.reportStatus]?.label }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="上报时间">{{ detailData.reportTime }}</el-descriptions-item>
<el-descriptions-item label="重试次数">{{ detailData.retryCount }}</el-descriptions-item>
</el-descriptions>
<div style="margin-top: 16px">
<div style="font-weight: bold; margin-bottom: 8px">上报数据:</div>
<el-input v-model="detailData.reportData" type="textarea" :rows="6" readonly />
</div>
<div v-if="detailData.responseData" style="margin-top: 12px">
<div style="font-weight: bold; margin-bottom: 8px">响应数据:</div>
<el-input v-model="detailData.responseData" type="textarea" :rows="4" readonly />
</div>
<div v-if="detailData.errorMessage" style="margin-top: 12px">
<div style="font-weight: bold; margin-bottom: 8px; color: #f56c6c">错误信息:</div>
<el-input v-model="detailData.errorMessage" type="textarea" :rows="3" readonly />
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { listHqmsReports, submitHqmsReport } from '@/api/mrhomepage'
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(false)
const dataList = ref([])
const total = ref(0)
const queryParams = reactive({ homepageId: '', reportStatus: '', pageNum: 1, pageSize: 10 })
const detailVisible = ref(false)
const detailData = ref({})
const statusMap = {
PENDING: { label: '待上报', type: 'info' },
SUBMITTED: { label: '已提交', type: 'warning' },
SUCCESS: { label: '成功', type: 'success' },
FAILED: { label: '失败', type: 'danger' }
}
const getList = async () => {
loading.value = true
try {
if (queryParams.homepageId) {
const res = await listHqmsReports(queryParams.homepageId)
let rows = res.data || []
if (queryParams.reportStatus) {
rows = rows.filter(r => r.reportStatus === queryParams.reportStatus)
}
dataList.value = rows
total.value = rows.length
} else {
dataList.value = []
total.value = 0
}
} catch (e) {
ElMessage.error('查询失败')
} finally {
loading.value = false
}
}
const handleQuery = () => {
queryParams.pageNum = 1
getList()
}
const resetQuery = () => {
queryParams.homepageId = ''
queryParams.reportStatus = ''
getList()
}
const handleDetail = (row) => {
detailData.value = row
detailVisible.value = true
}
const handleRetry = async (row) => {
await ElMessageBox.confirm('确认重新上报该记录?', '提示', { type: 'warning' })
try {
await submitHqmsReport(row.homepageId, row.reportType)
ElMessage.success('重新上报已提交')
getList()
} catch (e) {
ElMessage.error('上报失败')
}
}
onMounted(() => { getList() })
</script>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>