版本号 | 修改内容 | 修改日期 | 修改人 |
---|---|---|---|
V1.0 | 初始版本设计 | 2024.12.19 | 系统架构师 |
采用3表设计方案,通过通用化设计支持所有工伤接口类型:
作用:记录每一次工伤接口的调用情况,包括请求参数、响应结果、调用状态等 应用场景:所有工伤接口调用都要记录,用于日志追踪、问题排查、数据分析
CREATE TABLE WorkInjury_Interface_Log (
-- 主键信息
LogId BIGINT IDENTITY(1,1) PRIMARY KEY,
-- 业务关联信息(用于关联HIS系统的病人和就诊)
HisPatientId VARCHAR(50), -- HIS系统病人ID(病人表主键)
HisVisitNo VARCHAR(50), -- HIS系统就诊流水号/住院号
WorkInjuryTransactionId VARCHAR(50), -- 工伤系统交易流水号
-- 接口调用信息(标识调用的是哪个工伤接口)
InterfaceCode VARCHAR(10) NOT NULL, -- 接口编号:9001签到,2201登记,2207结算等
InterfaceName VARCHAR(100), -- 接口名称:签到,门诊登记,费用结算等
InterfaceCategory VARCHAR(20), -- 接口分类:认证类,业务类,体检类,对账类,下载类
-- 调用参数记录(完整保存调用的入参和返回参数)
RequestData NTEXT, -- 请求参数JSON:调用工伤接口时的完整入参
ResponseData NTEXT, -- 响应参数JSON:工伤接口返回的完整数据
-- 调用结果信息(判断接口调用是否成功)
CallResult BIT NOT NULL DEFAULT 0, -- 调用结果:0失败,1成功
ResultCode VARCHAR(20), -- 结果编码:工伤接口返回的错误码
ResultMessage NVARCHAR(500), -- 结果描述:错误信息或成功提示
-- 工伤系统返回的关键信息(用于后续业务关联)
WorkInjuryMsgId VARCHAR(50), -- 工伤系统报文ID:用于冲正等操作
WorkInjurySignNo VARCHAR(50), -- 工伤系统签到流水号:业务操作必需
WorkInjuryQualificationId VARCHAR(50), -- 工伤资格审核ID:登记时返回的资格ID
-- 时间和操作信息(记录操作过程信息)
CallTime DATETIME NOT NULL DEFAULT GETDATE(), -- 调用时间:接口实际调用的时间
OperatorId VARCHAR(50), -- 操作员ID:谁操作的
OperatorName NVARCHAR(50), -- 操作员姓名:操作员中文名
TerminalId VARCHAR(50), -- 终端ID:哪台机器操作的
-- 扩展字段(用于存储特殊数据)
Remark NVARCHAR(500), -- 备注:额外说明信息
ExtendData NTEXT, -- 扩展数据JSON:特殊业务数据
-- 创建和修改信息
CreateTime DATETIME NOT NULL DEFAULT GETDATE(),
UpdateTime DATETIME NOT NULL DEFAULT GETDATE()
);
-- 创建索引(提高查询性能)
CREATE INDEX IX_WorkInjury_Interface_Log_Patient ON WorkInjury_Interface_Log(HisPatientId, CallTime);
CREATE INDEX IX_WorkInjury_Interface_Log_Visit ON WorkInjury_Interface_Log(HisVisitNo, InterfaceCode);
CREATE INDEX IX_WorkInjury_Interface_Log_Interface ON WorkInjury_Interface_Log(InterfaceCode, CallTime);
CREATE INDEX IX_WorkInjury_Interface_Log_MsgId ON WorkInjury_Interface_Log(WorkInjuryMsgId);
作用:管理HIS系统病人与工伤系统的对应关系,判断病人是否为工伤性质 应用场景:病人登记时判断是否需要调用工伤接口,读卡时建立关联关系
CREATE TABLE WorkInjury_Patient_Relation (
-- 主键信息
RelationId BIGINT IDENTITY(1,1) PRIMARY KEY,
-- HIS系统病人信息(关联HIS病人表)
HisPatientId VARCHAR(50) NOT NULL, -- HIS系统病人ID:HIS病人表主键
HisCardNo VARCHAR(50), -- HIS病人卡号:院内就诊卡号
HisPatientName NVARCHAR(50), -- HIS病人姓名:在HIS中的姓名
HisIdCard VARCHAR(30), -- HIS身份证号:HIS中的身份证
-- 工伤系统病人信息(来自工伤读卡接口返回数据)
WorkInjuryCardNo VARCHAR(50), -- 工伤社保卡号:工伤系统的卡号
WorkInjuryPsnNo VARCHAR(50), -- 工伤个人唯一识别码:工伤系统病人标识
WorkInjuryPsnName NVARCHAR(50), -- 工伤系统病人姓名:工伤系统中的姓名
WorkInjuryIdCard VARCHAR(30), -- 工伤系统身份证号:可能与HIS不完全一致
WorkInjuryInsuranceArea VARCHAR(20), -- 工伤保险统筹区:如"宿迁市"
-- 工伤资格信息(判断病人工伤性质和资格)
IsWorkInjuryPatient BIT NOT NULL DEFAULT 0, -- 是否工伤病人:0否,1是
WorkInjuryType VARCHAR(20), -- 工伤类型:门诊,住院,体检等
QualificationId VARCHAR(50), -- 工伤医疗费资格审核信息ID
QualificationStatus VARCHAR(20), -- 资格审核状态:有效,过期,暂停等
QualificationExpireDate DATETIME, -- 资格有效期:过期需重新审核
-- 最近读卡信息(记录最新的工伤读卡数据)
LastReadCardTime DATETIME, -- 最后读卡时间:最近一次成功读卡时间
LastReadCardData NTEXT, -- 最后读卡返回数据JSON:完整的读卡结果
-- 状态信息
Status CHAR(1) NOT NULL DEFAULT '1', -- 状态:1有效,0无效,2暂停
-- 扩展字段
Remark NVARCHAR(500), -- 备注:特殊说明
ExtendData NTEXT, -- 扩展数据JSON:其他相关数据
-- 创建和修改信息
CreateTime DATETIME NOT NULL DEFAULT GETDATE(),
UpdateTime DATETIME NOT NULL DEFAULT GETDATE()
);
-- 创建索引和约束
CREATE UNIQUE INDEX IX_WorkInjury_Patient_Relation_HisPatient ON WorkInjury_Patient_Relation(HisPatientId);
CREATE INDEX IX_WorkInjury_Patient_Relation_WorkInjury ON WorkInjury_Patient_Relation(WorkInjuryPsnNo, WorkInjuryCardNo);
CREATE INDEX IX_WorkInjury_Patient_Relation_IdCard ON WorkInjury_Patient_Relation(WorkInjuryIdCard);
作用:存储具体的工伤业务数据,如登记信息、结算信息、处方明细、体检数据等 应用场景:保存业务办理的详细数据,用于业务查询、数据统计、单据打印等
CREATE TABLE WorkInjury_Business_Data (
-- 主键信息
BusinessId BIGINT IDENTITY(1,1) PRIMARY KEY,
-- 关联信息(与前两个表关联)
LogId BIGINT, -- 关联接口调用记录表ID:哪次接口调用产生的数据
HisPatientId VARCHAR(50), -- HIS系统病人ID:对应病人表
HisVisitNo VARCHAR(50), -- HIS系统就诊号:门诊号或住院号
-- 业务类型信息(标识业务类型)
BusinessType VARCHAR(20) NOT NULL, -- 业务类型:登记,结算,处方,体检,对账,下载等
BusinessCode VARCHAR(10), -- 业务编码:2201,2207,2204,8104,1320等
BusinessStatus VARCHAR(20), -- 业务状态:成功,失败,撤销,已冲正等
-- 工伤系统业务关键信息(来自工伤接口返回)
WorkInjuryRegisterNo VARCHAR(50), -- 工伤登记流水号:登记成功后返回
WorkInjurySettleNo VARCHAR(50), -- 工伤结算流水号:结算成功后返回
WorkInjuryPreSettleId VARCHAR(50), -- 工伤预结算ID:预结算返回的ID
-- 费用信息(结算相关业务使用)
TotalFee DECIMAL(15,2), -- 总费用:本次业务涉及的总金额
WorkInjuryFee DECIMAL(15,2), -- 工伤基金支付:工伤保险支付金额
PersonalFee DECIMAL(15,2), -- 个人支付:病人自付金额
HospitalFee DECIMAL(15,2), -- 医院垫付:医院需要垫付的金额
-- 业务详细数据(JSON格式存储复杂数据)
BusinessDetailData NTEXT, -- 业务明细数据JSON:处方明细,费用明细,体检项目等
HisBusinessData NTEXT, -- HIS系统业务数据JSON:HIS相关的业务数据
WorkInjuryResponseData NTEXT, -- 工伤接口完整返回JSON:完整的工伤接口返回数据
-- 相关单据信息(用于单据管理)
HisReceiptNo VARCHAR(50), -- HIS发票号:HIS系统生成的发票号
WorkInjuryReceiptNo VARCHAR(50), -- 工伤结算单号:工伤系统的结算单号
HisPrescriptionNo VARCHAR(50), -- HIS处方号:处方相关业务使用
-- 时间信息
BusinessTime DATETIME, -- 业务发生时间:实际业务办理时间
-- 撤销和冲正信息(业务可能需要撤销)
IsReversed BIT DEFAULT 0, -- 是否已冲正:0否,1是
ReversedLogId BIGINT, -- 冲正操作的日志ID:关联到冲正接口调用记录
ReversedTime DATETIME, -- 冲正时间:什么时候冲正的
ReversedReason NVARCHAR(200), -- 冲正原因:为什么要冲正
-- 扩展字段
Remark NVARCHAR(500), -- 备注:业务特殊说明
ExtendData NTEXT, -- 扩展数据JSON:其他业务数据
-- 创建和修改信息
CreateTime DATETIME NOT NULL DEFAULT GETDATE(),
UpdateTime DATETIME NOT NULL DEFAULT GETDATE()
);
-- 创建索引
CREATE INDEX IX_WorkInjury_Business_Data_LogId ON WorkInjury_Business_Data(LogId);
CREATE INDEX IX_WorkInjury_Business_Data_Patient ON WorkInjury_Business_Data(HisPatientId, BusinessTime);
CREATE INDEX IX_WorkInjury_Business_Data_Visit ON WorkInjury_Business_Data(HisVisitNo, BusinessType);
CREATE INDEX IX_WorkInjury_Business_Data_WorkInjury ON WorkInjury_Business_Data(WorkInjuryRegisterNo, WorkInjurySettleNo);
CREATE INDEX IX_WorkInjury_Business_Data_Business ON WorkInjury_Business_Data(BusinessType, BusinessStatus, BusinessTime);
HIS病人表 (His_Patient)
↓ (1:1)
工伤病人关联表 (WorkInjury_Patient_Relation)
↓ (1:N)
工伤接口调用记录表 (WorkInjury_Interface_Log)
↓ (1:N)
工伤业务数据记录表 (WorkInjury_Business_Data)
HIS病人表 ← → 工伤病人关联表
HisPatientId
工伤病人关联表 → 工伤接口调用记录表
HisPatientId
工伤接口调用记录表 → 工伤业务数据记录表
LogId
病人挂号/就诊
↓
判断是否工伤病人 (查询WorkInjury_Patient_Relation)
↓ (是工伤)
调用工伤接口 → 记录到WorkInjury_Interface_Log
↓ (成功)
解析业务数据 → 存储到WorkInjury_Business_Data
↓
调用HIS接口 → 存储HIS业务数据
场景描述:工伤病人来医院看门诊,需要先在工伤系统登记,再在HIS系统登记
数据操作流程:
-- 1. 判断是否工伤病人
SELECT IsWorkInjuryPatient, WorkInjuryPsnNo, QualificationId
FROM WorkInjury_Patient_Relation
WHERE HisPatientId = 'P12345' AND Status = '1';
-- 如果是工伤病人,继续以下步骤:
-- 2. 调用工伤登记接口后,记录接口调用日志
INSERT INTO WorkInjury_Interface_Log (
HisPatientId, HisVisitNo, InterfaceCode, InterfaceName, InterfaceCategory,
RequestData, ResponseData, CallResult, ResultCode, ResultMessage,
WorkInjuryMsgId, WorkInjuryQualificationId, CallTime, OperatorId, OperatorName
) VALUES (
'P12345', 'MZ2024120100001', '2201', '门诊/住院登记', '业务类',
'{"infno":"2201","psn_no":"32123456789","med_type":"11",...}',
'{"infcode":"0","output":{"ipt_otp_no":"WS2024120100001"},...}',
1, '0', '登记成功',
'SQ201348202412011030001', 'WS2024120100001', GETDATE(), 'DOC001', '张医生'
);
-- 3. 记录业务数据
INSERT INTO WorkInjury_Business_Data (
LogId, HisPatientId, HisVisitNo, BusinessType, BusinessCode, BusinessStatus,
WorkInjuryRegisterNo, BusinessDetailData, BusinessTime
) VALUES (
@@IDENTITY, 'P12345', 'MZ2024120100001', '登记', '2201', '成功',
'WS2024120100001', '{"med_type":"11","adm_dept":"内科",...}', GETDATE()
);
场景描述:工伤病人看完病,需要先在工伤系统结算,确定报销金额,再在HIS收费
-- 1. 处方明细上传
INSERT INTO WorkInjury_Interface_Log (...) VALUES (...); -- 接口调用日志
INSERT INTO WorkInjury_Business_Data (
LogId, HisPatientId, HisVisitNo, BusinessType, BusinessCode,
BusinessDetailData, BusinessTime
) VALUES (
@@IDENTITY, 'P12345', 'MZ2024120100001', '处方', '2204',
'{"feedetail":[{"med_list_codg":"A01.01.001","med_name":"阿莫西林",...}]}',
GETDATE()
);
-- 2. 费用预结算
INSERT INTO WorkInjury_Interface_Log (...) VALUES (...);
INSERT INTO WorkInjury_Business_Data (
LogId, HisPatientId, HisVisitNo, BusinessType, BusinessCode,
WorkInjuryPreSettleId, TotalFee, WorkInjuryFee, PersonalFee,
BusinessDetailData, BusinessTime
) VALUES (
@@IDENTITY, 'P12345', 'MZ2024120100001', '预结算', '2206',
'PRE20241201001', 150.00, 120.00, 30.00,
'{"pre_settle_data":{"psn_part_amt":30.00,"fund_pay_amt":120.00}}',
GETDATE()
);
-- 3. 正式结算
INSERT INTO WorkInjury_Interface_Log (...) VALUES (...);
INSERT INTO WorkInjury_Business_Data (
LogId, HisPatientId, HisVisitNo, BusinessType, BusinessCode,
WorkInjurySettleNo, TotalFee, WorkInjuryFee, PersonalFee,
WorkInjuryReceiptNo, BusinessTime
) VALUES (
@@IDENTITY, 'P12345', 'MZ2024120100001', '结算', '2207',
'SET20241201001', 150.00, 120.00, 30.00,
'WS2024120100001', GETDATE()
);
场景描述:每日对账,下载工伤系统的结算汇总数据
-- 总额对账接口调用
INSERT INTO WorkInjury_Interface_Log (
InterfaceCode, InterfaceName, InterfaceCategory,
RequestData, ResponseData, CallResult,
BusinessDetailData, CallTime, OperatorId
) VALUES (
'1320', '总额对账', '对账类',
'{"stmt_begndate":"20241201","stmt_enddate":"20241201"}',
'{"infcode":"0","output":{"total_cnt":15,"total_amt":2250.00}}',
1,
'{"account_date":"20241201","total_count":15,"total_amount":2250.00}',
GETDATE(), 'SYS001'
);
-- 对账业务数据记录
INSERT INTO WorkInjury_Business_Data (
LogId, BusinessType, BusinessCode, BusinessStatus,
TotalFee, BusinessDetailData, BusinessTime
) VALUES (
@@IDENTITY, '对账', '1320', '成功',
2250.00, '{"account_date":"20241201","detail_count":15}', GETDATE()
);
场景描述:下载费用明细数据,用于对账核实
-- 费用明细下载
INSERT INTO WorkInjury_Interface_Log (
InterfaceCode, InterfaceName, InterfaceCategory,
RequestData, ResponseData, CallResult,
BusinessDetailData, CallTime
) VALUES (
'9103', '费用明细详细信息下载', '下载类',
'{"queryCond":{"stmt_begndate":"20241201","stmt_enddate":"20241201"}}',
'{"infcode":"0","output":{"feedetail":[...]}}',
1,
'{"download_date":"20241201","record_count":156}',
GETDATE()
);
<!DOCTYPE html>
<html>
<head>
<title>工伤病人登记</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div class="patient-register">
<h3>工伤病人登记</h3>
<!-- 病人信息 -->
<div class="patient-info">
<label>病人ID: <input type="text" id="patientId" value="P12345"></label>
<label>就诊卡号: <input type="text" id="cardNo"></label>
<label>病人姓名: <input type="text" id="patientName"></label>
<button id="checkWorkInjury">检查工伤性质</button>
</div>
<!-- 工伤信息显示 -->
<div id="workInjuryInfo" style="display:none;">
<h4>工伤信息</h4>
<div id="workInjuryDetail"></div>
<button id="registerWorkInjury">工伤登记</button>
</div>
<!-- HIS登记 -->
<div class="his-register">
<h4>HIS系统登记</h4>
<label>科室: <select id="department"><option value="001">内科</option></select></label>
<label>医生: <select id="doctor"><option value="DOC001">张医生</option></select></label>
<button id="registerHis">HIS登记</button>
</div>
<!-- 结果显示 -->
<div id="result"></div>
</div>
<script>
$(document).ready(function() {
// 检查工伤性质
$('#checkWorkInjury').click(function() {
const patientId = $('#patientId').val();
$.ajax({
url: '/api/workinjury/checkPatient',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ patientId: patientId }),
success: function(response) {
if (response.success && response.data.isWorkInjuryPatient) {
// 是工伤病人,显示工伤信息
$('#workInjuryDetail').html(`
<p>工伤卡号: ${response.data.workInjuryCardNo}</p>
<p>个人识别码: ${response.data.workInjuryPsnNo}</p>
<p>资格状态: ${response.data.qualificationStatus}</p>
<p>有效期: ${response.data.qualificationExpireDate}</p>
`);
$('#workInjuryInfo').show();
} else {
// 非工伤病人,直接显示HIS登记
$('#result').html('<div class="info">该病人非工伤性质,直接进行HIS登记</div>');
}
},
error: function(xhr, status, error) {
$('#result').html('<div class="error">检查失败: ' + error + '</div>');
}
});
});
// 工伤登记
$('#registerWorkInjury').click(function() {
const patientId = $('#patientId').val();
const visitNo = 'MZ' + new Date().getFullYear() +
(new Date().getMonth() + 1).toString().padStart(2, '0') +
new Date().getDate().toString().padStart(2, '0') + '00001';
const registerData = {
action: "RegisterPatient",
businessParams: {
ipt_otp_no: visitNo,
med_type: "11", // 门诊
adm_time: new Date().toISOString().replace(/[-:]/g, '').slice(0, 14),
adm_dept_codg: $('#department').val(),
atddr_no: $('#doctor').val()
},
identifyMode: "1"
};
$.ajax({
url: '/api/workinjury/transaction',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(registerData),
success: function(response) {
if (response.success) {
$('#result').html('<div class="success">工伤登记成功,登记号: ' +
response.data.ipt_otp_no + '</div>');
// 工伤登记成功后,自动进行HIS登记
$('#registerHis').click();
} else {
$('#result').html('<div class="error">工伤登记失败: ' + response.message + '</div>');
}
}
});
});
// HIS登记
$('#registerHis').click(function() {
const hisRegisterData = {
patientId: $('#patientId').val(),
patientName: $('#patientName').val(),
department: $('#department').val(),
doctor: $('#doctor').val(),
visitType: '门诊'
};
$.ajax({
url: '/api/his/register',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(hisRegisterData),
success: function(response) {
if (response.success) {
$('#result').append('<div class="success">HIS登记成功,就诊号: ' +
response.data.visitNo + '</div>');
} else {
$('#result').append('<div class="error">HIS登记失败: ' + response.message + '</div>');
}
}
});
});
});
</script>
<style>
.patient-register { padding: 20px; }
.patient-info, .his-register { margin: 15px 0; padding: 10px; border: 1px solid #ddd; }
label { display: block; margin: 5px 0; }
input, select { margin-left: 10px; padding: 5px; }
button { margin: 10px 5px; padding: 8px 15px; background: #007cba; color: white; border: none; cursor: pointer; }
.success { color: green; font-weight: bold; }
.error { color: red; font-weight: bold; }
.info { color: blue; font-weight: bold; }
</style>
</body>
</html>
<template>
<div class="work-injury-settlement">
<h3>工伤费用结算</h3>
<!-- 病人选择 -->
<div class="patient-select">
<el-form :model="patientForm" inline>
<el-form-item label="就诊号">
<el-input v-model="patientForm.visitNo" placeholder="请输入就诊号"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadPatientInfo">加载病人信息</el-button>
</el-form-item>
</el-form>
</div>
<!-- 病人信息显示 -->
<div v-if="patientInfo.patientId" class="patient-info">
<h4>病人信息</h4>
<el-descriptions :column="3" border>
<el-descriptions-item label="病人姓名">{{ patientInfo.patientName }}</el-descriptions-item>
<el-descriptions-item label="就诊卡号">{{ patientInfo.cardNo }}</el-descriptions-item>
<el-descriptions-item label="工伤性质">
<el-tag :type="patientInfo.isWorkInjury ? 'success' : 'info'">
{{ patientInfo.isWorkInjury ? '工伤病人' : '普通病人' }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 处方明细 -->
<div class="prescription-detail">
<h4>处方明细</h4>
<el-table :data="prescriptionList" border>
<el-table-column prop="medName" label="药品名称"></el-table-column>
<el-table-column prop="quantity" label="数量"></el-table-column>
<el-table-column prop="unitPrice" label="单价"></el-table-column>
<el-table-column prop="totalPrice" label="小计"></el-table-column>
</el-table>
<div class="total-fee">
<strong>总金额: ¥{{ totalFee.toFixed(2) }}</strong>
</div>
</div>
<!-- 结算操作 -->
<div class="settlement-actions">
<el-button v-if="patientInfo.isWorkInjury"
type="warning"
@click="uploadPrescription"
:loading="uploading">
上传处方明细
</el-button>
<el-button v-if="patientInfo.isWorkInjury"
type="info"
@click="preSettle"
:loading="preSettling"
:disabled="!prescriptionUploaded">
工伤预结算
</el-button>
<el-button type="success"
@click="finalSettle"
:loading="settling">
{{ patientInfo.isWorkInjury ? '工伤正式结算' : 'HIS收费结算' }}
</el-button>
</div>
<!-- 结算结果 -->
<div v-if="settlementResult" class="settlement-result">
<h4>结算结果</h4>
<el-card>
<div v-if="patientInfo.isWorkInjury">
<p><strong>工伤结算单号:</strong> {{ settlementResult.workInjurySettleNo }}</p>
<p><strong>总费用:</strong> ¥{{ settlementResult.totalFee }}</p>
<p><strong>工伤基金支付:</strong> ¥{{ settlementResult.workInjuryFee }}</p>
<p><strong>个人支付:</strong> ¥{{ settlementResult.personalFee }}</p>
</div>
<div v-else>
<p><strong>HIS收费单号:</strong> {{ settlementResult.hisReceiptNo }}</p>
<p><strong>应收金额:</strong> ¥{{ settlementResult.totalFee }}</p>
</div>
</el-card>
</div>
<!-- 操作记录 -->
<div class="operation-log">
<h4>操作记录</h4>
<el-timeline>
<el-timeline-item
v-for="log in operationLogs"
:key="log.id"
:timestamp="log.time"
:type="log.success ? 'success' : 'danger'">
{{ log.operation }}: {{ log.message }}
</el-timeline-item>
</el-timeline>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
// 响应式数据
const patientForm = reactive({
visitNo: ''
})
const patientInfo = reactive({
patientId: '',
patientName: '',
cardNo: '',
isWorkInjury: false,
workInjuryPsnNo: '',
qualificationId: ''
})
const prescriptionList = ref([
{ medName: '阿莫西林胶囊', quantity: 2, unitPrice: 25.00, totalPrice: 50.00 },
{ medName: '布洛芬片', quantity: 1, unitPrice: 18.50, totalPrice: 18.50 },
{ medName: '维生素C片', quantity: 1, unitPrice: 12.80, totalPrice: 12.80 }
])
const uploading = ref(false)
const preSettling = ref(false)
const settling = ref(false)
const prescriptionUploaded = ref(false)
const settlementResult = ref(null)
const operationLogs = ref([])
// 计算属性
const totalFee = computed(() => {
return prescriptionList.value.reduce((sum, item) => sum + item.totalPrice, 0)
})
// 方法
const loadPatientInfo = async () => {
try {
const response = await fetch('/api/workinjury/getPatientInfo', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ visitNo: patientForm.visitNo })
})
const result = await response.json()
if (result.success) {
Object.assign(patientInfo, result.data)
addOperationLog('加载病人信息', '成功', true)
} else {
ElMessage.error('加载病人信息失败: ' + result.message)
addOperationLog('加载病人信息', result.message, false)
}
} catch (error) {
ElMessage.error('加载病人信息异常: ' + error.message)
addOperationLog('加载病人信息', error.message, false)
}
}
const uploadPrescription = async () => {
uploading.value = true
try {
const prescriptionData = {
action: "UploadPrescription",
businessParams: {
feedetail: prescriptionList.value.map(item => ({
med_list_codg: item.medCode || 'A01.01.001',
med_name: item.medName,
med_qty: item.quantity,
pric: item.unitPrice,
det_item_fee_sumamt: item.totalPrice
}))
}
}
const response = await fetch('/api/workinjury/transaction', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(prescriptionData)
})
const result = await response.json()
if (result.success) {
prescriptionUploaded.value = true
ElMessage.success('处方明细上传成功')
addOperationLog('上传处方明细', '成功', true)
} else {
ElMessage.error('处方明细上传失败: ' + result.message)
addOperationLog('上传处方明细', result.message, false)
}
} catch (error) {
ElMessage.error('处方明细上传异常: ' + error.message)
addOperationLog('上传处方明细', error.message, false)
} finally {
uploading.value = false
}
}
const preSettle = async () => {
preSettling.value = true
try {
const preSettleData = {
action: "PreSettle",
businessParams: {
ipt_otp_no: patientForm.visitNo,
psn_no: patientInfo.workInjuryPsnNo,
total_amt: totalFee.value
}
}
const response = await fetch('/api/workinjury/transaction', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(preSettleData)
})
const result = await response.json()
if (result.success) {
ElMessage.success('工伤预结算成功')
addOperationLog('工伤预结算',
`个人支付: ¥${result.data.psn_part_amt}, 基金支付: ¥${result.data.fund_pay_amt}`,
true)
} else {
ElMessage.error('工伤预结算失败: ' + result.message)
addOperationLog('工伤预结算', result.message, false)
}
} catch (error) {
ElMessage.error('工伤预结算异常: ' + error.message)
addOperationLog('工伤预结算', error.message, false)
} finally {
preSettling.value = false
}
}
const finalSettle = async () => {
settling.value = true
try {
if (patientInfo.isWorkInjury) {
// 工伤正式结算
const settleData = {
action: "Settle",
businessParams: {
ipt_otp_no: patientForm.visitNo,
psn_no: patientInfo.workInjuryPsnNo
}
}
const response = await fetch('/api/workinjury/transaction', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(settleData)
})
const result = await response.json()
if (result.success) {
settlementResult.value = {
workInjurySettleNo: result.data.setl_id,
totalFee: result.data.medfee_sumamt,
workInjuryFee: result.data.fund_pay_amt,
personalFee: result.data.psn_part_amt
}
ElMessage.success('工伤结算成功')
addOperationLog('工伤正式结算', '成功', true)
// 工伤结算成功后,调用HIS收费
await hisSettle()
} else {
ElMessage.error('工伤结算失败: ' + result.message)
addOperationLog('工伤正式结算', result.message, false)
}
} else {
// 直接HIS收费
await hisSettle()
}
} catch (error) {
ElMessage.error('结算异常: ' + error.message)
addOperationLog('结算操作', error.message, false)
} finally {
settling.value = false
}
}
const hisSettle = async () => {
try {
const hisSettleData = {
visitNo: patientForm.visitNo,
totalFee: patientInfo.isWorkInjury ? settlementResult.value?.personalFee : totalFee.value,
paymentMethod: '现金'
}
const response = await fetch('/api/his/settle', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(hisSettleData)
})
const result = await response.json()
if (result.success) {
if (!settlementResult.value) {
settlementResult.value = {}
}
settlementResult.value.hisReceiptNo = result.data.receiptNo
if (!patientInfo.isWorkInjury) {
settlementResult.value.totalFee = totalFee.value
}
ElMessage.success('HIS收费成功')
addOperationLog('HIS收费', '成功', true)
} else {
ElMessage.error('HIS收费失败: ' + result.message)
addOperationLog('HIS收费', result.message, false)
}
} catch (error) {
ElMessage.error('HIS收费异常: ' + error.message)
addOperationLog('HIS收费', error.message, false)
}
}
const addOperationLog = (operation, message, success) => {
operationLogs.value.unshift({
id: Date.now(),
operation,
message,
success,
time: new Date().toLocaleString()
})
}
</script>
<style scoped>
.work-injury-settlement {
padding: 20px;
}
.patient-select, .patient-info, .prescription-detail,
.settlement-actions, .settlement-result, .operation-log {
margin: 20px 0;
}
.total-fee {
text-align: right;
margin: 10px 0;
font-size: 16px;
}
.settlement-actions {
text-align: center;
}
.settlement-actions .el-button {
margin: 0 10px;
}
</style>
package com.hospital.workinjury.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hospital.workinjury.entity.*;
import com.hospital.workinjury.repository.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
/**
* 工伤联网接口服务类
* 负责工伤接口调用和数据存储的核心业务逻辑
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WorkInjuryService {
private final WorkInjuryInterfaceLogRepository logRepository;
private final WorkInjuryPatientRelationRepository relationRepository;
private final WorkInjuryBusinessDataRepository businessRepository;
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
// 工伤接口地址配置
private static final String WORK_INJURY_API_URL = "http://localhost:8321/api/workinjury/transaction";
/**
* 检查病人工伤性质
* @param patientId HIS病人ID
* @return 工伤病人信息
*/
public WorkInjuryPatientInfo checkWorkInjuryPatient(String patientId) {
log.info("检查病人工伤性质, patientId: {}", patientId);
WorkInjuryPatientRelation relation = relationRepository.findByHisPatientId(patientId);
WorkInjuryPatientInfo info = new WorkInjuryPatientInfo();
info.setPatientId(patientId);
if (relation != null && relation.getIsWorkInjuryPatient()) {
info.setWorkInjuryPatient(true);
info.setWorkInjuryCardNo(relation.getWorkInjuryCardNo());
info.setWorkInjuryPsnNo(relation.getWorkInjuryPsnNo());
info.setQualificationId(relation.getQualificationId());
info.setQualificationStatus(relation.getQualificationStatus());
info.setQualificationExpireDate(relation.getQualificationExpireDate());
} else {
info.setWorkInjuryPatient(false);
}
return info;
}
/**
* 工伤病人登记流程
* 先调用工伤接口,成功后记录数据,再调用HIS接口
*/
@Transactional
public WorkInjuryRegisterResult registerWorkInjuryPatient(WorkInjuryRegisterRequest request) {
log.info("开始工伤病人登记流程, patientId: {}, visitNo: {}",
request.getPatientId(), request.getVisitNo());
WorkInjuryRegisterResult result = new WorkInjuryRegisterResult();
try {
// 1. 检查是否工伤病人
WorkInjuryPatientRelation relation = relationRepository.findByHisPatientId(request.getPatientId());
if (relation == null || !relation.getIsWorkInjuryPatient()) {
// 非工伤病人,直接调用HIS登记
return registerHisPatientOnly(request);
}
// 2. 调用工伤登记接口
WorkInjuryApiResult workInjuryResult = callWorkInjuryRegisterApi(request, relation);
// 3. 记录工伤接口调用日志
Long logId = saveInterfaceLog(request, workInjuryResult, "2201", "门诊/住院登记", "业务类");
if (workInjuryResult.isSuccess()) {
// 4. 记录工伤业务数据
saveBusinessData(logId, request, workInjuryResult, "登记", "2201");
// 5. 调用HIS登记接口
HisRegisterResult hisResult = callHisRegisterApi(request);
// 6. 组装返回结果
result.setSuccess(true);
result.setMessage("工伤病人登记成功");
result.setWorkInjuryRegisterNo(workInjuryResult.getData().get("ipt_otp_no").toString());
result.setHisVisitNo(hisResult.getVisitNo());
result.setQualificationId(workInjuryResult.getData().get("qualification_id").toString());
log.info("工伤病人登记成功, 工伤登记号: {}, HIS就诊号: {}",
result.getWorkInjuryRegisterNo(), result.getHisVisitNo());
} else {
result.setSuccess(false);
result.setMessage("工伤登记失败: " + workInjuryResult.getMessage());
log.error("工伤登记失败: {}", workInjuryResult.getMessage());
}
} catch (Exception e) {
log.error("工伤病人登记异常", e);
result.setSuccess(false);
result.setMessage("登记异常: " + e.getMessage());
}
return result;
}
/**
* 工伤费用结算流程
* 处方上传 -> 预结算 -> 正式结算 -> HIS收费
*/
@Transactional
public WorkInjurySettlementResult settleWorkInjuryFee(WorkInjurySettlementRequest request) {
log.info("开始工伤费用结算流程, visitNo: {}", request.getVisitNo());
WorkInjurySettlementResult result = new WorkInjurySettlementResult();
try {
// 1. 检查病人工伤性质
WorkInjuryPatientRelation relation = relationRepository.findByHisPatientId(request.getPatientId());
if (relation == null || !relation.getIsWorkInjuryPatient()) {
// 非工伤病人,直接HIS收费
return settleHisFeeOnly(request);
}
// 2. 上传处方明细
WorkInjuryApiResult uploadResult = uploadPrescriptionDetail(request);
if (!uploadResult.isSuccess()) {
result.setSuccess(false);
result.setMessage("处方上传失败: " + uploadResult.getMessage());
return result;
}
// 3. 费用预结算
WorkInjuryApiResult preSettleResult = preSettleFee(request);
if (!preSettleResult.isSuccess()) {
result.setSuccess(false);
result.setMessage("预结算失败: " + preSettleResult.getMessage());
return result;
}
// 4. 正式结算
WorkInjuryApiResult settleResult = settleFee(request);
if (!settleResult.isSuccess()) {
result.setSuccess(false);
result.setMessage("工伤结算失败: " + settleResult.getMessage());
return result;
}
// 5. 解析结算结果
Map<String, Object> settleData = settleResult.getData();
BigDecimal totalFee = new BigDecimal(settleData.get("medfee_sumamt").toString());
BigDecimal workInjuryFee = new BigDecimal(settleData.get("fund_pay_amt").toString());
BigDecimal personalFee = new BigDecimal(settleData.get("psn_part_amt").toString());
// 6. 记录结算业务数据
Long logId = saveInterfaceLog(request, settleResult, "2207", "费用结算", "业务类");
saveSettlementBusinessData(logId, request, settleResult, totalFee, workInjuryFee, personalFee);
// 7. HIS收费(只收个人支付部分)
HisSettlementResult hisResult = callHisSettlementApi(request, personalFee);
// 8. 组装返回结果
result.setSuccess(true);
result.setMessage("工伤费用结算成功");
result.setWorkInjurySettleNo(settleData.get("setl_id").toString());
result.setTotalFee(totalFee);
result.setWorkInjuryFee(workInjuryFee);
result.setPersonalFee(personalFee);
result.setHisReceiptNo(hisResult.getReceiptNo());
log.info("工伤费用结算成功, 结算号: {}, 总费用: {}, 个人支付: {}",
result.getWorkInjurySettleNo(), totalFee, personalFee);
} catch (Exception e) {
log.error("工伤费用结算异常", e);
result.setSuccess(false);
result.setMessage("结算异常: " + e.getMessage());
}
return result;
}
/**
* 对账接口调用
* 用于每日对账核对数据
*/
public WorkInjuryAccountResult dailyAccount(String accountDate) {
log.info("开始工伤对账, 对账日期: {}", accountDate);
WorkInjuryAccountResult result = new WorkInjuryAccountResult();
try {
// 1. 构造对账请求参数
Map<String, Object> accountParams = new HashMap<>();
accountParams.put("stmt_begndate", accountDate);
accountParams.put("stmt_enddate", accountDate);
WorkInjuryApiRequest apiRequest = new WorkInjuryApiRequest();
apiRequest.setAction("TotalAccount");
apiRequest.setBusinessParams(accountParams);
// 2. 调用工伤总额对账接口
WorkInjuryApiResult apiResult = callWorkInjuryApi(apiRequest);
// 3. 记录接口调用日志
Long logId = saveAccountInterfaceLog(apiRequest, apiResult, "1320", "总额对账", "对账类");
if (apiResult.isSuccess()) {
// 4. 解析对账结果
Map<String, Object> accountData = apiResult.getData();
int totalCount = Integer.parseInt(accountData.get("total_cnt").toString());
BigDecimal totalAmount = new BigDecimal(accountData.get("total_amt").toString());
// 5. 记录对账业务数据
saveAccountBusinessData(logId, accountDate, totalCount, totalAmount);
// 6. 组装返回结果
result.setSuccess(true);
result.setMessage("对账成功");
result.setAccountDate(accountDate);
result.setTotalCount(totalCount);
result.setTotalAmount(totalAmount);
log.info("工伤对账成功, 日期: {}, 笔数: {}, 金额: {}", accountDate, totalCount, totalAmount);
} else {
result.setSuccess(false);
result.setMessage("对账失败: " + apiResult.getMessage());
log.error("工伤对账失败: {}", apiResult.getMessage());
}
} catch (Exception e) {
log.error("工伤对账异常", e);
result.setSuccess(false);
result.setMessage("对账异常: " + e.getMessage());
}
return result;
}
/**
* 下载费用明细数据
* 用于对账核实和数据分析
*/
public WorkInjuryDownloadResult downloadFeeDetail(String beginDate, String endDate) {
log.info("开始下载工伤费用明细, 开始日期: {}, 结束日期: {}", beginDate, endDate);
WorkInjuryDownloadResult result = new WorkInjuryDownloadResult();
try {
// 1. 构造下载请求参数
Map<String, Object> queryCondition = new HashMap<>();
queryCondition.put("stmt_begndate", beginDate);
queryCondition.put("stmt_enddate", endDate);
Map<String, Object> downloadParams = new HashMap<>();
downloadParams.put("queryCond", queryCondition);
WorkInjuryApiRequest apiRequest = new WorkInjuryApiRequest();
apiRequest.setAction("QueryFeeDetail");
apiRequest.setBusinessParams(downloadParams);
// 2. 调用工伤费用明细下载接口
WorkInjuryApiResult apiResult = callWorkInjuryApi(apiRequest);
// 3. 记录接口调用日志
Long logId = saveDownloadInterfaceLog(apiRequest, apiResult, "9103", "费用明细详细信息下载", "下载类");
if (apiResult.isSuccess()) {
// 4. 解析下载结果
Map<String, Object> downloadData = apiResult.getData();
@SuppressWarnings("unchecked")
java.util.List<Map<String, Object>> feeDetailList =
(java.util.List<Map<String, Object>>) downloadData.get("feedetail");
int recordCount = feeDetailList != null ? feeDetailList.size() : 0;
// 5. 记录下载业务数据
saveDownloadBusinessData(logId, beginDate, endDate, recordCount, feeDetailList);
// 6. 组装返回结果
result.setSuccess(true);
result.setMessage("下载成功");
result.setBeginDate(beginDate);
result.setEndDate(endDate);
result.setRecordCount(recordCount);
result.setFeeDetailList(feeDetailList);
log.info("工伤费用明细下载成功, 记录数: {}", recordCount);
} else {
result.setSuccess(false);
result.setMessage("下载失败: " + apiResult.getMessage());
log.error("工伤费用明细下载失败: {}", apiResult.getMessage());
}
} catch (Exception e) {
log.error("工伤费用明细下载异常", e);
result.setSuccess(false);
result.setMessage("下载异常: " + e.getMessage());
}
return result;
}
// ==================== 私有辅助方法 ====================
/**
* 调用工伤登记接口
*/
private WorkInjuryApiResult callWorkInjuryRegisterApi(
WorkInjuryRegisterRequest request,
WorkInjuryPatientRelation relation) throws Exception {
Map<String, Object> businessParams = new HashMap<>();
businessParams.put("ipt_otp_no", request.getVisitNo());
businessParams.put("med_type", "11"); // 门诊
businessParams.put("adm_time", LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
businessParams.put("adm_dept_codg", request.getDepartmentCode());
businessParams.put("atddr_no", request.getDoctorCode());
businessParams.put("psn_no", relation.getWorkInjuryPsnNo());
businessParams.put("qualification_id", relation.getQualificationId());
WorkInjuryApiRequest apiRequest = new WorkInjuryApiRequest();
apiRequest.setAction("RegisterPatient");
apiRequest.setBusinessParams(businessParams);
apiRequest.setIdentifyMode("1");
return callWorkInjuryApi(apiRequest);
}
/**
* 统一的工伤接口调用方法
*/
private WorkInjuryApiResult callWorkInjuryApi(WorkInjuryApiRequest request) throws Exception {
log.debug("调用工伤接口, action: {}", request.getAction());
ResponseEntity<Map> response = restTemplate.postForEntity(
WORK_INJURY_API_URL,
request,
Map.class
);
Map<String, Object> responseBody = response.getBody();
WorkInjuryApiResult result = new WorkInjuryApiResult();
result.setSuccess((Boolean) responseBody.get("success"));
result.setCode((String) responseBody.get("code"));
result.setMessage((String) responseBody.get("message"));
if (result.isSuccess()) {
@SuppressWarnings("unchecked")
Map<String, Object> data = (Map<String, Object>) responseBody.get("data");
result.setData(data);
}
return result;
}
/**
* 保存接口调用日志
*/
private Long saveInterfaceLog(
Object request,
WorkInjuryApiResult apiResult,
String interfaceCode,
String interfaceName,
String interfaceCategory) {
try {
WorkInjuryInterfaceLog log = new WorkInjuryInterfaceLog();
// 设置基本信息
if (request instanceof WorkInjuryRegisterRequest) {
WorkInjuryRegisterRequest registerRequest = (WorkInjuryRegisterRequest) request;
log.setHisPatientId(registerRequest.getPatientId());
log.setHisVisitNo(registerRequest.getVisitNo());
} else if (request instanceof WorkInjurySettlementRequest) {
WorkInjurySettlementRequest settlementRequest = (WorkInjurySettlementRequest) request;
log.setHisPatientId(settlementRequest.getPatientId());
log.setHisVisitNo(settlementRequest.getVisitNo());
}
log.setInterfaceCode(interfaceCode);
log.setInterfaceName(interfaceName);
log.setInterfaceCategory(interfaceCategory);
// 设置请求和响应数据
log.setRequestData(objectMapper.writeValueAsString(request));
log.setResponseData(objectMapper.writeValueAsString(apiResult));
// 设置调用结果
log.setCallResult(apiResult.isSuccess());
log.setResultCode(apiResult.getCode());
log.setResultMessage(apiResult.getMessage());
// 设置工伤系统关键信息
if (apiResult.isSuccess() && apiResult.getData() != null) {
Map<String, Object> data = apiResult.getData();
log.setWorkInjuryMsgId((String) data.get("inf_refmsgid"));
log.setWorkInjuryQualificationId((String) data.get("qualification_id"));
}
// 设置时间和操作信息
log.setCallTime(LocalDateTime.now());
log.setOperatorId("SYSTEM");
log.setOperatorName("系统自动");
WorkInjuryInterfaceLog savedLog = logRepository.save(log);
return savedLog.getLogId();
} catch (Exception e) {
log.error("保存接口调用日志失败", e);
return null;
}
}
/**
* 保存业务数据
*/
private void saveBusinessData(
Long logId,
WorkInjuryRegisterRequest request,
WorkInjuryApiResult apiResult,
String businessType,
String businessCode) {
try {
WorkInjuryBusinessData businessData = new WorkInjuryBusinessData();
businessData.setLogId(logId);
businessData.setHisPatientId(request.getPatientId());
businessData.setHisVisitNo(request.getVisitNo());
businessData.setBusinessType(businessType);
businessData.setBusinessCode(businessCode);
businessData.setBusinessStatus("成功");
if (apiResult.getData() != null) {
Map<String, Object> data = apiResult.getData();
businessData.setWorkInjuryRegisterNo((String) data.get("ipt_otp_no"));
businessData.setBusinessDetailData(objectMapper.writeValueAsString(data));
}
businessData.setBusinessTime(LocalDateTime.now());
businessRepository.save(businessData);
} catch (Exception e) {
log.error("保存业务数据失败", e);
}
}
/**
* 保存结算业务数据
*/
private void saveSettlementBusinessData(
Long logId,
WorkInjurySettlementRequest request,
WorkInjuryApiResult apiResult,
BigDecimal totalFee,
BigDecimal workInjuryFee,
BigDecimal personalFee) {
try {
WorkInjuryBusinessData businessData = new WorkInjuryBusinessData();
businessData.setLogId(logId);
businessData.setHisPatientId(request.getPatientId());
businessData.setHisVisitNo(request.getVisitNo());
businessData.setBusinessType("结算");
businessData.setBusinessCode("2207");
businessData.setBusinessStatus("成功");
// 设置费用信息
businessData.setTotalFee(totalFee);
businessData.setWorkInjuryFee(workInjuryFee);
businessData.setPersonalFee(personalFee);
if (apiResult.getData() != null) {
Map<String, Object> data = apiResult.getData();
businessData.setWorkInjurySettleNo((String) data.get("setl_id"));
businessData.setWorkInjuryReceiptNo((String) data.get("setl_detail_id"));
businessData.setBusinessDetailData(objectMapper.writeValueAsString(data));
}
businessData.setBusinessTime(LocalDateTime.now());
businessRepository.save(businessData);
} catch (Exception e) {
log.error("保存结算业务数据失败", e);
}
}
// 其他辅助方法... (uploadPrescriptionDetail, preSettleFee, settleFee, callHisApi等)
// 为了节省篇幅,这里省略了部分辅助方法的实现
}
这个完整的文档涵盖了:
这套设计方案能够完美实现您的需求:工伤病人先调用工伤接口存储数据,再调用HIS接口;非工伤病人直接调用HIS接口,同时通过通用化设计支持所有类型的工伤接口,简单实用可靠。