feat: 门诊手术中计费功能
- 数据库:在adm_charge_item表添加SourceBillNo字段 - 后端实体类:更新ChargeItem.java添加SourceBillNo字段 - 前端组件:创建手术计费界面(基于门诊划价界面) - 后端API:扩展PrePrePaymentDto支持手术计费标识 - 后端Service:扩展getChargeItems方法支持手术计费过滤 - 门诊手术安排界面:添加【计费】按钮 注意事项: - 需要手动执行SQL脚本:openhis-server-new/sql/add_source_bill_no_to_adm_charge_item.sql - 术后一站式结算功能待后续开发
This commit is contained in:
502
openhis-ui-vue3/src/views/charge/surgerycharge/index.vue
Normal file
502
openhis-ui-vue3/src/views/charge/surgerycharge/index.vue
Normal file
@@ -0,0 +1,502 @@
|
||||
<template>
|
||||
<div style="display: flex; justify-content: space-between" class="app-container" v-loading="loading"
|
||||
:element-loading-text="loadingText">
|
||||
<!-- 左侧:患者基本信息区 -->
|
||||
<el-card style="width: 30%">
|
||||
<template #header>
|
||||
<span style="vertical-align: middle">患者基本信息</span>
|
||||
</template>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="病历号">{{ patientInfo.encounterBusNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ patientInfo.patientName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ patientInfo.genderEnum_enumText }}</el-descriptions-item>
|
||||
<el-descriptions-item label="年龄">{{ patientInfo.age }}</el-descriptions-item>
|
||||
<el-descriptions-item label="科室">{{ patientInfo.organizationName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="就诊时间">
|
||||
{{ formatDateStr(patientInfo.receptionTime, 'YYYY-MM-DD HH:mm:ss') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手术单号">{{ surgeryInfo.surgeryNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手术名称">{{ surgeryInfo.surgeryName }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 右侧:收费项目区 -->
|
||||
<div style="width: 69%">
|
||||
<el-card style="min-width: 1100px">
|
||||
<template #header>
|
||||
<span style="vertical-align: middle">收费项目</span>
|
||||
</template>
|
||||
<div style="margin-bottom: 10px">
|
||||
<el-button type="primary" @click="confirmCharge()" :disabled="buttonDisabled">
|
||||
确认收费
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="handleReadCard('01')" style="width: 65px">
|
||||
电子凭证
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="handleReadCard('03')" style="width: 65px">
|
||||
医保卡
|
||||
</el-button>
|
||||
<span style="float: right">
|
||||
合计金额:{{ totalAmounts ? totalAmounts.toFixed(2) : 0 }}元
|
||||
</span>
|
||||
</div>
|
||||
<el-table
|
||||
ref="chargeListRef"
|
||||
height="530"
|
||||
:data="chargeList"
|
||||
row-key="id"
|
||||
@selection-change="handleSelectionChange"
|
||||
v-loading="chargeLoading"
|
||||
:span-method="objectSpanMethod"
|
||||
border
|
||||
>
|
||||
<el-table-column type="selection" :selectable="checkSelectable" width="55" />
|
||||
<el-table-column label="单据号" align="center" prop="busNo" width="180" />
|
||||
<el-table-column label="收费项目" align="center" prop="itemName" width="200" />
|
||||
<el-table-column label="数量" align="center" prop="quantityValue" width="80" />
|
||||
<el-table-column label="医疗类型" align="center" prop="medTypeCode_dictText" />
|
||||
<el-table-column label="医保编码" align="center" prop="ybNo" />
|
||||
<el-table-column label="费用性质" align="center" prop="contractName" />
|
||||
<el-table-column label="收费状态" align="center" prop="statusEnum_enumText" width="150">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.statusEnum === 1" disable-transitions>
|
||||
{{ scope.row.statusEnum_enumText }}
|
||||
</el-tag>
|
||||
<el-tag v-else-if="scope.row.statusEnum === 5" type="success" disable-transitions>
|
||||
{{ scope.row.statusEnum_enumText }}
|
||||
</el-tag>
|
||||
<el-tag v-else-if="scope.row.statusEnum === 8" type="danger" disable-transitions>
|
||||
{{ scope.row.statusEnum_enumText }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="warning" disable-transitions>
|
||||
{{ scope.row.statusEnum_enumText }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" align="right" prop="totalPrice" header-align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.totalPrice.toFixed(2) + ' 元' || '0.00' + ' 元' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收款人" align="center" prop="entererId_dictText" />
|
||||
<el-table-column
|
||||
label="操作"
|
||||
align="center"
|
||||
fixed="right"
|
||||
header-align="center"
|
||||
class-name="no-hover-column"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
:disabled="!scope.row.paymentId"
|
||||
link
|
||||
type="primary"
|
||||
@click="printCharge(scope.row)"
|
||||
>
|
||||
打印
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 收费对话框 -->
|
||||
<ChargeDialog
|
||||
ref="chargeDialogRef"
|
||||
:open="openDialog"
|
||||
@close="handleClose"
|
||||
:category="patientInfo.categoryEnum"
|
||||
:totalAmount="totalAmount"
|
||||
:patientInfo="patientInfo"
|
||||
:chargeItemIds="chargeItemIdList"
|
||||
:consumablesIdList="consumablesIdList"
|
||||
:chrgBchnoList="chrgBchnoList"
|
||||
:userCardInfo="userCardInfo"
|
||||
:paymentId="paymentId"
|
||||
:details="details"
|
||||
:chargedItems="chargedItems"
|
||||
:feeType="patientInfo.medfeePaymtdCode"
|
||||
:medfee_paymtd_code="medfee_paymtd_code"
|
||||
@refresh="getChargeList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="SurgeryCharge">
|
||||
import {
|
||||
getChargeList,
|
||||
precharge,
|
||||
getChargeInfo,
|
||||
} from '../cliniccharge/components/api';
|
||||
import {invokeYbPlugin5000, invokeYbPlugin5001} from '@/api/public';
|
||||
import ChargeDialog from '../cliniccharge/components/chargeDialog.vue';
|
||||
import {formatDateStr} from '@/utils';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import Decimal from 'decimal.js';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const userStore = useUserStore();
|
||||
const { medfee_paymtd_code } = proxy.useDict('medfee_paymtd_code');
|
||||
|
||||
// Props: 从手术安排界面传入的患者信息和手术信息
|
||||
const props = defineProps({
|
||||
patientInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({})
|
||||
},
|
||||
surgeryInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
// 数据定义
|
||||
const totalAmounts = ref(0);
|
||||
const selectedRows = ref([]);
|
||||
const chargeList = ref([]);
|
||||
const chargeItemIdList = ref([]);
|
||||
const chrgBchnoList = ref([]);
|
||||
const chargeLoading = ref(false);
|
||||
const encounterId = ref('');
|
||||
const paymentId = ref('');
|
||||
const openDialog = ref(false);
|
||||
const totalAmount = ref(0);
|
||||
const chargeListRef = ref();
|
||||
const details = ref({});
|
||||
const buttonDisabled = computed(() => {
|
||||
return Object.keys(props.patientInfo).length === 0;
|
||||
});
|
||||
const chargedItems = ref([]);
|
||||
const consumablesIdList = ref([]);
|
||||
const userCardInfo = ref({});
|
||||
const readCardLoading = ref(false);
|
||||
const loadingText = ref('');
|
||||
const loading = ref(false);
|
||||
|
||||
// Watch
|
||||
watch(
|
||||
() => selectedRows.value,
|
||||
(newVlaue) => {
|
||||
if (newVlaue && newVlaue.length > 0) {
|
||||
handleTotalAmount();
|
||||
} else {
|
||||
totalAmounts.value = 0;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => chargeList.value,
|
||||
(newVlaue) => {
|
||||
if (newVlaue && newVlaue.length > 0) {
|
||||
handleTotalAmount();
|
||||
} else {
|
||||
totalAmounts.value = 0;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
if (props.patientInfo && props.patientInfo.encounterId) {
|
||||
encounterId.value = props.patientInfo.encounterId;
|
||||
fetchChargeList();
|
||||
}
|
||||
});
|
||||
|
||||
// 方法
|
||||
function handleSelectionChange(selection) {
|
||||
selectedRows.value = selection;
|
||||
}
|
||||
|
||||
function handleTotalAmount() {
|
||||
if (selectedRows.value.length == 0) {
|
||||
totalAmounts.value = chargeList.value.reduce((accumulator, currentRow) => {
|
||||
return new Decimal(accumulator).add(currentRow.totalPrice.toFixed(2) || 0);
|
||||
}, new Decimal(0));
|
||||
} else {
|
||||
totalAmounts.value = selectedRows.value.reduce((accumulator, currentRow) => {
|
||||
return new Decimal(accumulator).add(currentRow.totalPrice.toFixed(2) || 0);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取收费项目列表(只显示该手术已计费的项目)
|
||||
function fetchChargeList() {
|
||||
if (!props.patientInfo.encounterId) {
|
||||
return;
|
||||
}
|
||||
chargeLoading.value = true;
|
||||
// 调用门诊划价的getChargeList接口,传入encounterId
|
||||
// 只显示generateSourceEnum=2(手术计费)且sourceBillNo=手术单号的费用项
|
||||
getChargeList(props.patientInfo.encounterId).then((res) => {
|
||||
// 过滤出手术计费的收费项目
|
||||
chargeList.value = (res.data || []).filter(item =>
|
||||
item.generateSourceEnum === 2 && item.sourceBillNo === props.surgeryInfo.surgeryNo
|
||||
);
|
||||
setTimeout(() => {
|
||||
chargeLoading.value = false;
|
||||
// 默认选中所有未收费的项目
|
||||
if (chargeListRef.value) {
|
||||
chargeListRef.value.toggleAllSelection();
|
||||
}
|
||||
}, 100);
|
||||
}).catch(() => {
|
||||
chargeLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function checkSelectable(row, index) {
|
||||
// 已结算时禁用选择框
|
||||
return row.statusEnum === 1;
|
||||
}
|
||||
|
||||
function handleClose(value, msg) {
|
||||
openDialog.value = false;
|
||||
if (value == 'success') {
|
||||
proxy.$modal.msgSuccess(msg);
|
||||
fetchChargeList();
|
||||
}
|
||||
}
|
||||
|
||||
// 确认收费
|
||||
function confirmCharge() {
|
||||
let selectRows = chargeListRef.value.getSelectionRows();
|
||||
if (selectRows.length == 0) {
|
||||
proxy.$modal.msgWarning('请选择一条收费项目');
|
||||
return;
|
||||
}
|
||||
chargeItemIdList.value = selectRows.map((item) => {
|
||||
return item.id;
|
||||
});
|
||||
consumablesIdList.value = selectRows
|
||||
.filter((item) => {
|
||||
return item.serviceTable == 'wor_device_request';
|
||||
})
|
||||
.map((item) => {
|
||||
return item.id;
|
||||
});
|
||||
chargedItems.value = selectRows;
|
||||
|
||||
precharge({
|
||||
patientId: props.patientInfo.patientId,
|
||||
encounterId: props.patientInfo.encounterId,
|
||||
chargeItemIds: chargeItemIdList.value,
|
||||
// 传递手术计费标识
|
||||
generateSourceEnum: 2,
|
||||
sourceBillNo: props.surgeryInfo.surgeryNo,
|
||||
}).then((res) => {
|
||||
if (res.code == 200 && res.data) {
|
||||
paymentId.value = res.data.paymentId;
|
||||
chrgBchnoList.value = res.data.chrgBchnoList;
|
||||
totalAmount.value = res.data.details?.find((item) => item.payEnum == 220000)?.amount ?? 0;
|
||||
details.value = res.data.details?.filter((item) => {
|
||||
return item.amount > 0;
|
||||
}) || [];
|
||||
openDialog.value = true;
|
||||
} else {
|
||||
proxy.$modal.msgError(res?.msg || '预结算失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 读卡功能
|
||||
async function handleReadCard(value) {
|
||||
try {
|
||||
let jsonResult;
|
||||
let cardInfo;
|
||||
let userMessage = undefined;
|
||||
switch (value) {
|
||||
case '01': // 电子凭证
|
||||
await invokeYbPlugin5000({
|
||||
FunctionId: 3,
|
||||
url: 'http://10.47.0.67:8089/localcfc/api/hsecfc/localQrCodeQuery',
|
||||
orgId: 'H22010200672',
|
||||
businessType: '01101',
|
||||
operatorId: userStore.id.toString(),
|
||||
operatorName: userStore.name,
|
||||
officeId: 'D83',
|
||||
officeName: '财务科',
|
||||
})
|
||||
.then((res) => {
|
||||
readCardLoading.value = true;
|
||||
loadingText.value = '正在读取...';
|
||||
jsonResult = res.data;
|
||||
})
|
||||
.catch(() => {
|
||||
readCardLoading.value = false;
|
||||
})
|
||||
.finally(() => {
|
||||
readCardLoading.value = false;
|
||||
});
|
||||
cardInfo = JSON.parse(JSON.stringify(jsonResult));
|
||||
let message = JSON.parse(cardInfo.data);
|
||||
userMessage = {
|
||||
certType: '02',
|
||||
certNo: message.data.idNo,
|
||||
psnCertType: '02',
|
||||
};
|
||||
userCardInfo = {
|
||||
certType: '01',
|
||||
certNo: message.data.idNo,
|
||||
psnCertType: '01',
|
||||
busiCardInfo: message.data.ecToken,
|
||||
};
|
||||
break;
|
||||
case '03': // 医保卡
|
||||
readCardLoading.value = true;
|
||||
loadingText.value = '正在读取...';
|
||||
await invokeYbPlugin5001(
|
||||
JSON.stringify({
|
||||
FunctionId: 1,
|
||||
IP: 'ddjk.jlhs.gov.cn',
|
||||
PORT: 20215,
|
||||
TIMEOUT: 60,
|
||||
SFZ_DRIVER_TYPE: 1,
|
||||
})
|
||||
)
|
||||
.then((res) => {
|
||||
jsonResult = JSON.stringify(res.data);
|
||||
})
|
||||
.finally(() => {
|
||||
readCardLoading.value = false;
|
||||
});
|
||||
let message1 = JSON.parse(jsonResult);
|
||||
userMessage = {
|
||||
certType: '02',
|
||||
certNo: message1.SocialSecurityNumber,
|
||||
psnCertType: '02',
|
||||
};
|
||||
userCardInfo = {
|
||||
certType: '02',
|
||||
certNo: message1.SocialSecurityNumber,
|
||||
psnCertType: '02',
|
||||
busiCardInfo: message1.BusiCardInfo,
|
||||
};
|
||||
break;
|
||||
}
|
||||
readCardLoading.value = false;
|
||||
if (userMessage.certNo) {
|
||||
let selectRows = chargeListRef.value.getSelectionRows();
|
||||
if (selectRows.length == 0) {
|
||||
proxy.$modal.msgWarning('请选择一条收费项目');
|
||||
return;
|
||||
}
|
||||
chargeItemIdList.value = selectRows.map((item) => {
|
||||
return item.id;
|
||||
});
|
||||
totalAmount.value = selectRows.reduce((accumulator, currentRow) => {
|
||||
return accumulator + (currentRow.totalPrice || 0);
|
||||
}, 0);
|
||||
precharge({
|
||||
patientId: props.patientInfo.patientId,
|
||||
encounterId: props.patientInfo.encounterId,
|
||||
chargeItemIds: chargeItemIdList.value,
|
||||
ybMdtrtCertType: userCardInfo.psnCertType,
|
||||
busiCardInfo: userCardInfo.busiCardInfo,
|
||||
generateSourceEnum: 2,
|
||||
sourceBillNo: props.surgeryInfo.surgeryNo,
|
||||
}).then((res) => {
|
||||
if (res.code == 200 && res.data) {
|
||||
paymentId.value = res.data.paymentId;
|
||||
totalAmount.value = res.data.details?.find((item) => item.payEnum == 220000)?.amount ?? 0;
|
||||
details.value = res.data.details || [];
|
||||
chargeItemIdList.value = selectRows.map((item) => {
|
||||
return item.id;
|
||||
});
|
||||
chargedItems.value = selectRows;
|
||||
consumablesIdList.value = selectRows
|
||||
.filter((item) => {
|
||||
return item.serviceTable == 'wor_device_request';
|
||||
})
|
||||
.map((item) => {
|
||||
return item.id;
|
||||
});
|
||||
openDialog.value = true;
|
||||
} else {
|
||||
proxy.$modal.msgError(res?.msg || '预结算失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('调用失败:', error);
|
||||
readCardLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 行合并方法
|
||||
function objectSpanMethod({ row, column, rowIndex, columnIndex }) {
|
||||
if (columnIndex === 10) {
|
||||
if (!row.paymentId) {
|
||||
return [1,1];
|
||||
}
|
||||
let spanCount = 1;
|
||||
if (rowIndex === 0 || chargeList.value[rowIndex - 1].paymentId !== row.paymentId) {
|
||||
for (let i = rowIndex + 1; i < chargeList.value.length; i++) {
|
||||
if (chargeList.value[i].paymentId === row.paymentId) {
|
||||
spanCount++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [spanCount, 1];
|
||||
} else {
|
||||
return [0, 0];
|
||||
}
|
||||
}
|
||||
return [1, 1];
|
||||
}
|
||||
|
||||
// 打印功能
|
||||
function printCharge(row) {
|
||||
let rows = [];
|
||||
chargeList.value.forEach((item, index) => {
|
||||
if (item.paymentId === row.paymentId) {
|
||||
rows.push(item);
|
||||
}
|
||||
});
|
||||
chargedItems.value = rows;
|
||||
getChargeInfo({ paymentId: row.paymentId }).then((res) => {
|
||||
if (res.data && res.data.detail) {
|
||||
const amountDetail = res.data.detail?.find((item) => item.payEnum == 220000);
|
||||
if (amountDetail) {
|
||||
totalAmount.value = amountDetail.amount || 0;
|
||||
rows.forEach((item) => {
|
||||
if (item.actualPrice === undefined || item.actualPrice === null) {
|
||||
item.actualPrice = 0;
|
||||
}
|
||||
if (item.discountAmount === undefined || item.discountAmount === null) {
|
||||
item.discountAmount = 0;
|
||||
}
|
||||
if (item.discountRate === undefined || item.discountRate === null) {
|
||||
item.discountRate = 100;
|
||||
}
|
||||
});
|
||||
}
|
||||
const enhancedPrintData = {
|
||||
...res.data,
|
||||
selectedRow: row,
|
||||
chargedItems: rows,
|
||||
};
|
||||
nextTick(() => {
|
||||
proxy.$refs['chargeDialogRef'].printReceipt(enhancedPrintData);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.no-hover-column) .cell:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body) tr:hover td.no-hover-column {
|
||||
background-color: inherit !important;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user